"""Async Python client for the GeoScored API."""

import asyncio

import httpx

from .errors import GeoScoredError
from .models import BatchResult, Check, CheckList, CompareResult, ExportData, Report, Scan, ScanList


class GeoScoredClient:
    """Async client for the GeoScored GEO audit API.

    Usage::

        async with GeoScoredClient(api_key="geo_...") as client:
            scan = await client.create_scan("https://example.com", brand_name="Example")
            scan = await client.poll_scan(scan.id)
            report = await client.get_report(scan.id)
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://geoscored.ai",
        timeout: float = 30.0,
    ):
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self._client = httpx.AsyncClient(
            base_url=f"{self.base_url}/api/v1",
            headers={"X-API-Key": api_key},
            timeout=timeout,
        )

    async def __aenter__(self) -> "GeoScoredClient":
        return self

    async def __aexit__(self, *args) -> None:
        await self.close()

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

    async def _request(self, method: str, path: str, **kwargs) -> httpx.Response:
        """Make an API request and handle errors."""
        resp = await self._client.request(method, path, **kwargs)
        if resp.status_code >= 400:
            try:
                body = resp.json()
            except Exception:
                body = {}
            raise GeoScoredError.from_response(resp.status_code, body)
        return resp

    async def create_scan(
        self,
        url: str,
        brand_name: str = "",
        callback_url: str | None = None,
    ) -> Scan:
        """Create a new GEO audit scan."""
        body: dict = {"url": url, "brand_name": brand_name}
        if callback_url:
            body["callback_url"] = callback_url
        resp = await self._request("POST", "/scans", json=body)
        return Scan.model_validate(resp.json())

    async def get_scan(self, scan_id: str) -> Scan:
        """Get a scan by ID."""
        resp = await self._request("GET", f"/scans/{scan_id}")
        return Scan.model_validate(resp.json())

    async def list_scans(
        self,
        limit: int = 50,
        status: str | None = None,
        starting_after: str | None = None,
    ) -> ScanList:
        """List scans with optional filtering and cursor pagination."""
        params: dict = {"limit": limit}
        if status:
            params["status"] = status
        if starting_after:
            params["starting_after"] = starting_after
        resp = await self._request("GET", "/scans", params=params)
        return ScanList.model_validate(resp.json())

    async def get_report(self, scan_id: str) -> Report:
        """Get the full audit report for a completed scan."""
        resp = await self._request("GET", f"/scans/{scan_id}/report")
        return Report.model_validate(resp.json())

    async def list_checks(self, scan_id: str) -> CheckList:
        """List all check results for a completed scan."""
        resp = await self._request("GET", f"/scans/{scan_id}/checks")
        return CheckList.model_validate(resp.json())

    async def get_check(self, scan_id: str, check_id: str) -> Check:
        """Get a specific check result."""
        resp = await self._request("GET", f"/scans/{scan_id}/checks/{check_id}")
        return Check.model_validate(resp.json())

    async def delete_scan(self, scan_id: str) -> None:
        """Delete a scan and its results."""
        await self._request("DELETE", f"/scans/{scan_id}")

    async def batch_create_scans(
        self,
        scans: list[dict],
        callback_url: str | None = None,
    ) -> BatchResult:
        """Create multiple scans in a single request (up to 25).

        Args:
            scans: List of dicts with 'url' and optional 'brand_name' keys.
            callback_url: Optional shared HTTPS webhook URL for all scans.
        """
        body: dict = {"scans": scans}
        if callback_url:
            body["callback_url"] = callback_url
        resp = await self._request("POST", "/scans/batch", json=body)
        return BatchResult.model_validate(resp.json())

    async def compare_scans(self, scan_id_a: str, scan_id_b: str) -> CompareResult:
        """Compare two completed scans and return a structured diff.

        Args:
            scan_id_a: ID of the first scan.
            scan_id_b: ID of the second scan.
        """
        resp = await self._request(
            "POST", "/scans/compare", json={"scan_ids": [scan_id_a, scan_id_b]}
        )
        return CompareResult.model_validate(resp.json())

    async def export_scans(self, days: int = 30) -> ExportData:
        """Export scan history as JSON."""
        resp = await self._request("GET", "/scans/export", params={"days": days})
        return ExportData.model_validate(resp.json())

    async def poll_scan(
        self,
        scan_id: str,
        interval: float = 2.0,
        timeout: float = 300.0,
    ) -> Scan:
        """Poll a scan until it reaches a terminal status (complete or failed).

        Args:
            scan_id: The scan to poll.
            interval: Seconds between polls.
            timeout: Maximum seconds to wait before raising TimeoutError.

        Returns:
            The completed (or failed) Scan object.
        """
        elapsed = 0.0
        while elapsed < timeout:
            scan = await self.get_scan(scan_id)
            if scan.status in ("complete", "failed"):
                return scan
            await asyncio.sleep(interval)
            elapsed += interval
        raise TimeoutError(f"Scan {scan_id} did not complete within {timeout}s")
