"""Synchronous client for the Gibs compliance API."""

from __future__ import annotations

import json
import os
import time
from collections.abc import Iterator
from typing import Any

import httpx

from gibs.exceptions import GibsAPIError, GibsAuthError, GibsError, GibsRateLimitError
from gibs.types import (
    APIKey,
    CheckRequest,
    CheckResponse,
    ClassifyRequest,
    ClassifyResponse,
    CreateKeyResponse,
    DeleteKeyResponse,
    HealthResponse,
    StreamChunk,
    StreamComplete,
    StreamDone,
    StreamError,
    StreamEvent,
    StreamStatus,
)

_DEFAULT_BASE_URL = "https://api.gibs.dev"
_DEFAULT_TIMEOUT = 120.0
_DEFAULT_MAX_RETRIES = 3


class GibsClient:
    """Synchronous client for the Gibs compliance API.

    Usage::

        from gibs import GibsClient

        client = GibsClient(api_key="gbs_live_xxx")
        result = client.classify("Facial recognition for airport security")
        print(result.risk_level)
    """

    def __init__(
        self,
        api_key: str | None = None,
        base_url: str | None = None,
        timeout: float = _DEFAULT_TIMEOUT,
        max_retries: int = _DEFAULT_MAX_RETRIES,
    ) -> None:
        self.api_key = api_key or os.environ.get("GIBS_API_KEY")
        if not self.api_key:
            raise GibsError(
                "No API key provided. Pass api_key= or set the GIBS_API_KEY environment variable."
            )

        self.base_url = (base_url or os.environ.get("GIBS_BASE_URL") or _DEFAULT_BASE_URL).rstrip(
            "/"
        )
        self.timeout = timeout
        self.max_retries = max_retries

        self._client = httpx.Client(
            base_url=self.base_url,
            headers={
                "Authorization": f"Bearer {self.api_key}",
                "Content-Type": "application/json",
                "User-Agent": "gibs-python/0.3.0",
            },
            timeout=httpx.Timeout(self.timeout),
        )

    def close(self) -> None:
        """Close the underlying HTTP client."""
        self._client.close()

    def __enter__(self) -> GibsClient:
        return self

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

    # --- Core endpoints ---

    def classify(
        self,
        description: str,
        *,
        data_types: list[str] | None = None,
        decision_scope: str | None = None,
        sector: str | None = None,
        jurisdiction: str = "EU",
    ) -> ClassifyResponse:
        """Classify an AI system's risk level under EU regulation.

        Args:
            description: Description of the AI system.
            data_types: Types of data processed (e.g. ["biometric", "health"]).
            decision_scope: Scope of decisions made (e.g. "employment").
            sector: Industry sector (e.g. "healthcare").
            jurisdiction: Legal jurisdiction. Defaults to "EU".

        Returns:
            Classification result with risk level, obligations, and sources.
        """
        body = ClassifyRequest(
            description=description,
            data_types=data_types,
            decision_scope=decision_scope,
            sector=sector,
            jurisdiction=jurisdiction,
        )
        data = self._request("POST", "/v1/classify", json=body.model_dump(exclude_none=True))
        return ClassifyResponse.model_validate(data)

    def check(
        self,
        question: str,
        *,
        system_context: dict | None = None,  # type: ignore[type-arg]
        regulation: str = "auto",
        response_format: str = "text",
    ) -> CheckResponse:
        """Ask a compliance question and get an answer with citations.

        Args:
            question: The compliance question to answer.
            system_context: Optional context about your AI system.
            regulation: Target regulation: "ai_act", "gdpr", "dora", or "auto".
            response_format: "text" (default) or "structured" for parsed JSON sections.

        Returns:
            Answer with confidence level, sources, and abstention info.
        """
        body = CheckRequest(
            question=question,
            system_context=system_context,
            regulation=regulation,
            response_format=response_format,
        )
        data = self._request("POST", "/v1/check", json=body.model_dump(exclude_none=True))
        return CheckResponse.model_validate(data)

    def check_stream(
        self,
        question: str,
        *,
        system_context: dict | None = None,  # type: ignore[type-arg]
        regulation: str = "auto",
    ) -> Iterator[StreamEvent]:
        """Stream a compliance answer via Server-Sent Events.

        Yields events as they arrive: status updates, text chunks,
        and final metadata. First token arrives in ~3-5s instead of
        waiting ~20s for the full response.

        Args:
            question: The compliance question to answer.
            system_context: Optional context about your AI system.
            regulation: Target regulation: "ai_act", "gdpr", "dora", or "auto".

        Yields:
            StreamEvent objects (StreamStatus, StreamChunk, StreamComplete, StreamDone, StreamError).
        """
        body = CheckRequest(
            question=question,
            system_context=system_context,
            regulation=regulation,
        )
        with self._client.stream(
            "POST",
            "/v1/check/stream",
            json=body.model_dump(exclude_none=True),
        ) as response:
            if response.status_code >= 400:
                response.read()
                self._handle_response(response)

            for line in response.iter_lines():
                if not line.startswith("data: "):
                    continue
                raw = line[6:]
                try:
                    event = json.loads(raw)
                except json.JSONDecodeError:
                    continue
                yield _parse_stream_event(event)

    def health(self) -> HealthResponse:
        """Check the health of the Gibs API.

        Returns:
            Health status including component states and corpus version.
        """
        data = self._request("GET", "/v1/health")
        return HealthResponse.model_validate(data)

    # --- Account endpoints ---

    def list_keys(self) -> list[APIKey]:
        """List all API keys for the authenticated account.

        Returns:
            List of API key summaries (secrets are not included).
        """
        data = self._request("GET", "/v1/account/keys")
        return [APIKey.model_validate(k) for k in data]

    def create_key(self, name: str = "Default") -> CreateKeyResponse:
        """Create a new API key.

        Args:
            name: A human-readable name for the key.

        Returns:
            The created key including the full secret (shown only once).
        """
        data = self._request("POST", "/v1/account/keys", json={"name": name})
        return CreateKeyResponse.model_validate(data)

    def delete_key(self, key_id: int) -> DeleteKeyResponse:
        """Revoke an API key.

        Args:
            key_id: The ID of the key to revoke.

        Returns:
            Confirmation of revocation.
        """
        data = self._request("DELETE", f"/v1/account/keys/{key_id}")
        return DeleteKeyResponse.model_validate(data)

    # --- Internal ---

    def _request(
        self,
        method: str,
        path: str,
        *,
        json: dict | None = None,  # type: ignore[type-arg]
    ) -> Any:
        """Make an HTTP request with retry logic and error handling."""
        last_exc: Exception | None = None

        for attempt in range(self.max_retries + 1):
            try:
                response = self._client.request(method, path, json=json)
                return self._handle_response(response)
            except (GibsRateLimitError, httpx.TransportError) as exc:
                last_exc = exc
                if attempt == self.max_retries:
                    break
                delay = self._backoff_delay(attempt, exc)
                time.sleep(delay)
            except GibsAPIError:
                raise

        if isinstance(last_exc, GibsRateLimitError):
            raise last_exc
        raise GibsError(f"Request failed after {self.max_retries + 1} attempts: {last_exc}")

    @staticmethod
    def _handle_response(response: httpx.Response) -> Any:
        """Parse response or raise appropriate error."""
        if response.status_code == 401:
            body = _safe_json(response)
            msg = body.get("detail", "Invalid or missing API key") if isinstance(body, dict) else str(body)
            raise GibsAuthError(message=msg, response_body=body)

        if response.status_code == 429:
            body = _safe_json(response)
            retry_after = response.headers.get("Retry-After")
            raise GibsRateLimitError(
                message="Rate limit exceeded",
                retry_after=float(retry_after) if retry_after else None,
                response_body=body,
            )

        if response.status_code >= 400:
            body = _safe_json(response)
            msg = body.get("detail", response.text) if isinstance(body, dict) else response.text
            raise GibsAPIError(
                message=msg,
                status_code=response.status_code,
                response_body=body,
            )

        return response.json()

    @staticmethod
    def _backoff_delay(attempt: int, exc: Exception) -> float:
        """Calculate exponential backoff delay in seconds."""
        if isinstance(exc, GibsRateLimitError) and exc.retry_after:
            return exc.retry_after
        return min(2**attempt * 0.5, 30.0)


def _safe_json(response: httpx.Response) -> Any:
    """Try to parse JSON from response, return raw text on failure."""
    try:
        return response.json()
    except Exception:
        return response.text


def _parse_stream_event(event: dict) -> StreamEvent:  # type: ignore[type-arg]
    """Parse a raw SSE event dict into a typed StreamEvent."""
    event_type = event.get("type", "")
    if event_type == "status":
        return StreamStatus(data=event.get("data", {}))
    elif event_type == "chunk":
        return StreamChunk(data=event.get("data", ""))
    elif event_type == "complete":
        return StreamComplete(data=event.get("data", {}))
    elif event_type == "done":
        return StreamDone(data=event.get("data", {}))
    elif event_type == "error":
        return StreamError(data=event.get("data", ""))
    else:
        return StreamStatus(data=event)
