"""TrueSecurix Python SDK.

    from truesecurix import TrueSecurix
    client = TrueSecurix("tsx_live_xxx")
    result = client.verify(selfie="selfie.jpg", document="aadhaar.jpg", reference="user_123")
    print(result["decision"], result["risk_score"])

The API key is a server-side secret: use this SDK only from your backend, never in a
browser, mobile app, or public code. Account, billing, and key management are done from
the dashboard with an email login (a session), not with an API key, so they are not part
of this SDK by design: a leaked API key must not reach your account.
"""

from __future__ import annotations

import time

import httpx

__all__ = ["TrueSecurix", "TrueSecurixError"]

DEFAULT_BASE_URL = "https://truesecurix.com"
_RETRY_STATUS = {429, 502, 503, 504}


class TrueSecurixError(Exception):
    """An API error. Carries the HTTP status, the machine-readable error code, a
    message, and the request id you can quote to support."""

    def __init__(self, status_code: int, code: str = "", message: str = "",
                 request_id: str = ""):
        self.status_code = status_code
        self.code = code
        self.request_id = request_id
        super().__init__(f"[{status_code}] {code}: {message}"
                         + (f" (request_id={request_id})" if request_id else ""))


class TrueSecurix:
    def __init__(self, api_key: str, base_url: str = DEFAULT_BASE_URL,
                 timeout: float = 60.0, max_retries: int = 2):
        if not api_key:
            raise ValueError("api_key is required")
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.max_retries = max_retries
        self._client = httpx.Client(timeout=timeout)

    # -- context manager so the connection pool is released -----------------------
    def __enter__(self) -> "TrueSecurix":
        return self

    def __exit__(self, *exc) -> None:
        self.close()

    def close(self) -> None:
        self._client.close()

    def _raise(self, r: httpx.Response) -> None:
        code, message = "", r.text[:200]
        try:
            body = r.json()
            err = body.get("error") or {}
            code = err.get("code", "")
            message = err.get("message") or body.get("detail") or message
        except Exception:
            pass
        raise TrueSecurixError(r.status_code, code, message,
                               r.headers.get("X-Request-ID", ""))

    def verify(self, selfie: str | None = None, document: str | None = None,
               reference: str | None = None, idempotency_key: str | None = None) -> dict:
        """Run a fraud-risk verification on a selfie and/or an ID document image.

        Pass `reference` (your end-user or case id) and it is echoed back. Pass
        `idempotency_key` to make retries safe: a repeat with the same key returns the
        original result and is never charged twice. Transient engine errors and rate
        limits are retried with backoff. Raises TrueSecurixError on a final failure.
        """
        if not selfie and not document:
            raise ValueError("Provide a selfie and/or a document")
        headers = {"X-API-Key": self.api_key}
        if idempotency_key:
            headers["Idempotency-Key"] = idempotency_key
        data = {"reference": reference} if reference else None

        last_exc: Exception | None = None
        for attempt in range(self.max_retries + 1):
            opened = []
            try:
                files = {}
                if selfie:
                    fh = open(selfie, "rb"); opened.append(fh)
                    files["selfie"] = (selfie, fh, "image/jpeg")
                if document:
                    fh = open(document, "rb"); opened.append(fh)
                    files["document"] = (document, fh, "image/jpeg")
                r = self._client.post(f"{self.base_url}/v1/verify",
                                      headers=headers, files=files, data=data)
                if r.status_code == 200:
                    return r.json()
                if r.status_code in _RETRY_STATUS and attempt < self.max_retries:
                    time.sleep(0.5 * (2 ** attempt))
                    continue
                self._raise(r)
            except (httpx.TransportError, httpx.TimeoutException) as exc:
                last_exc = exc
                if attempt < self.max_retries:
                    time.sleep(0.5 * (2 ** attempt))
                    continue
                raise TrueSecurixError(503, "engine_unavailable",
                                       f"Could not reach TrueSecurix: {exc}") from exc
            finally:
                for fh in opened:
                    fh.close()
        if last_exc:
            raise TrueSecurixError(503, "engine_unavailable", str(last_exc))
        raise TrueSecurixError(500, "unknown", "Verification failed")
