"""Python 3.10+ server example using only the standard library."""
import json
import math
from http.client import IncompleteRead
from email.utils import parsedate_to_datetime
import time
import urllib.error
import urllib.request
import uuid


class ApiError(Exception):
    def __init__(self, status, code, message, request_id=None, retry_after=0):
        super().__init__(message)
        self.status, self.code, self.request_id = status, code, request_id
        self.retry_after = retry_after


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None  # Never forward your Authorization header to a redirect target.


def retry_after_seconds(value):
    if not value:
        return 0
    try:
        seconds = int(value) if value.strip().isdigit() else parsedate_to_datetime(value).timestamp() - time.time()
        return max(0, seconds)
    except (ValueError, TypeError, OverflowError):
        return 0


def request(base_url, api_key, path, body=None, operation_id=None, attempts=3, delay=1):
    if not isinstance(attempts, int) or not 1 <= attempts <= 10 or not math.isfinite(delay) or delay < 0:
        raise ValueError('Use 1–10 attempts and a nonnegative finite delay')
    headers = {'Authorization': 'Bearer ' + api_key, 'Accept': 'application/json'}
    if body is not None:
        if not operation_id:
            raise ValueError('Persist an operation ID before submitting')
        headers.update({'Content-Type': 'application/json', 'Idempotency-Key': operation_id})
    data = json.dumps(body).encode() if body is not None else None
    opener = urllib.request.build_opener(NoRedirect())
    for attempt in range(attempts):
        wait = delay * 2 ** attempt
        try:
            req = urllib.request.Request(base_url.rstrip('/') + '/api/v1/' + path, data=data, headers=headers)
            try:
                response = opener.open(req, timeout=60)
            except urllib.error.HTTPError as error:
                response = error
            with response:
                request_id = response.headers.get('X-Request-ID')
                retry_after = retry_after_seconds(response.headers.get('Retry-After'))
                try:
                    result = json.loads(response.read())
                except (ValueError, UnicodeError):
                    raise ApiError(response.code, 'invalid_response', 'Unreadable response. Keep the operation ID for recovery.', request_id, retry_after)
                if 200 <= response.code < 300:
                    return result
                error = result.get('error', {})
                raise ApiError(response.code, error.get('code', 'invalid_request' if 'detail' in result else 'http_error'),
                               error.get('message', result.get('message', 'Check the request fields.')), request_id, retry_after)
        except ApiError as error:
            wait = max(wait, error.retry_after)
            if error.status not in (429, 502, 503, 504) or attempt == attempts - 1 or wait > 60:
                raise
        except (urllib.error.URLError, OSError, IncompleteRead):
            if attempt == attempts - 1:
                raise
        if attempt < attempts - 1:
            time.sleep(wait)


def get_account(base_url, api_key, **options):
    return request(base_url, api_key, 'account', **options)


def run_operation(base_url, api_key, operation, **options):
    if not operation['vat_number'].strip():
        raise ValueError('Enter a VAT number')
    mode = operation['mode']
    if mode not in ('format', 'registry', 'lookup'):
        raise ValueError('Choose a supported operation')
    body = {'vat_number': operation['vat_number']}
    if mode != 'lookup':
        body['mode'] = mode
    operation_id = operation.get('id') or str(uuid.uuid4())
    return request(base_url, api_key, 'lookups' if mode == 'lookup' else 'checks',
                   body=body, operation_id=operation_id, **options)
