"""One bounded request; no redirects, retry, proxy environment, or persistent event state."""
import asyncio
import ssl
import httpx
from control_sidecar.protocol import BoundaryError, MAX_BODY


class Transport:
    def __init__(self, ca, *, client=None):
        self.client = client or httpx.AsyncClient(
            verify=ssl.create_default_context(cafile=ca), trust_env=False, follow_redirects=False,
            timeout=httpx.Timeout(1.5, connect=0.75, pool=0.1),
            limits=httpx.Limits(max_connections=40, max_keepalive_connections=8))
        self.closed = False

    async def request(self, method, url, *, body=None, token=None, seconds=1.5, retry_after_error=False):
        headers = {'Accept': 'application/json', 'Accept-Encoding': 'identity'}
        if body is not None:
            headers['Content-Type'] = 'application/json'
        if token:
            headers['Authorization'] = 'Bearer ' + token
        try:
            async with asyncio.timeout(seconds):
                async with self.client.stream(method, url, content=body, headers=headers) as r:
                    if r.headers.get('content-encoding', 'identity') != 'identity':
                        raise BoundaryError('UPSTREAM_ENCODING_REFUSED', 502)
                    raw = bytearray()
                    async for chunk in r.aiter_bytes():
                        if len(raw) + len(chunk) > MAX_BODY:
                            raise BoundaryError('UPSTREAM_BODY_TOO_LARGE', 502)
                        raw.extend(chunk)
                    if 300 <= r.status_code < 400:
                        raise BoundaryError('UPSTREAM_REDIRECT_REFUSED', 502)
                    if retry_after_error and r.status_code in (429, 503) and r.headers.get('retry-after'):
                        from email.utils import parsedate_to_datetime
                        import time
                        value = r.headers['retry-after']
                        try:
                            delay = float(value) if value.isdigit() else parsedate_to_datetime(value).timestamp() - time.time()
                        except (ValueError, TypeError, OverflowError):
                            delay = 60
                        error = BoundaryError('UPSTREAM_RETRY_LATER', r.status_code)
                        error.retry_after = max(0, delay)
                        raise error
                    return r.status_code, bytes(raw)
        except (httpx.HTTPError, TimeoutError) as exc:
            if 'CERTIFICATE_VERIFY_FAILED' in str(exc):
                raise BoundaryError('UPSTREAM_TLS_INVALID', 502) from None
            # A timeout is unknown outcome, not evidence that the peer did not execute.
            raise BoundaryError('UPSTREAM_OUTCOME_UNKNOWN', 502) from None

    async def close(self):
        if not self.closed:
            self.closed = True
            await self.client.aclose()
