"""AUTX Python Client — programmatic access to the AUTX gateway."""

from __future__ import annotations

import time
from typing import Any, BinaryIO

import httpx

from autx_client.types import (
    AgentListItem,
    AgentManifest,
    AgentProfileData,
    AgentResponse,
    OrderResponse,
    OrderResult,
    ProxyResponse,
    _parse_agent_list_item,
    _parse_agent_response,
)

DEFAULT_BASE_URL = "https://app-autx-api-prod.azurewebsites.net/api/v1"
DEFAULT_TIMEOUT = 120
MAX_RETRIES = 3
RETRY_BACKOFF = 1.0


class AutxClient:
    """Synchronous AUTX API client."""

    def __init__(
        self,
        api_key: str,
        base_url: str = DEFAULT_BASE_URL,
        timeout: int = DEFAULT_TIMEOUT,
        max_retries: int = MAX_RETRIES,
    ):
        if not api_key.startswith("autx_live_"):
            raise ValueError("API key must start with 'autx_live_'")
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.max_retries = max_retries

    def _headers(self) -> dict[str, str]:
        return {"Authorization": f"Bearer {self.api_key}"}

    def _request(self, method: str, path: str, **kwargs) -> httpx.Response:
        """Make a request with retry logic."""
        url = f"{self.base_url}{path}"
        last_exc = None
        for attempt in range(self.max_retries):
            try:
                with httpx.Client(timeout=self.timeout) as client:
                    resp = client.request(method, url, headers=self._headers(), **kwargs)
                    if resp.status_code == 429:
                        retry_after = int(resp.headers.get("Retry-After", "5"))
                        time.sleep(retry_after)
                        continue
                    return resp
            except httpx.TimeoutException as e:
                last_exc = e
                time.sleep(RETRY_BACKOFF * (attempt + 1))
            except httpx.HTTPError as e:
                last_exc = e
                break
        raise last_exc or RuntimeError("Request failed after retries")

    # --- Buyer methods ---

    def proxy(self, ticker_or_id: str, prompt: str | None = None, body: dict | None = None) -> ProxyResponse:
        """Send a request through the AUTX proxy (free, no billing).

        Args:
            ticker_or_id: Agent ticker or UUID
            prompt: Text prompt (convenience — wraps in {"prompt": prompt})
            body: Raw JSON body to forward (overrides prompt if both given)
        """
        json_body = body or {"prompt": prompt}
        resp = self._request("POST", f"/proxy/{ticker_or_id}", json=json_body)
        resp.raise_for_status()
        return ProxyResponse(
            content=resp.content,
            status_code=resp.status_code,
            content_type=resp.headers.get("content-type", ""),
            headers=dict(resp.headers),
            request_id=resp.headers.get("x-autx-request-id", ""),
            latency_ms=int(resp.headers.get("x-autx-latency-ms", "0")),
            agent_ticker=resp.headers.get("x-autx-agent", ""),
        )

    def order(
        self,
        agent_id: str,
        prompt: str,
        payment_method: str = "stripe",
        files: list[tuple[str, BinaryIO]] | None = None,
    ) -> OrderResponse:
        """Create a paid service order.

        Args:
            agent_id: Agent UUID
            prompt: Text prompt
            payment_method: "stripe" or "usdc"
            files: Optional list of (filename, file_object) tuples for multipart
        """
        if files:
            multipart = [
                ("agent_id", (None, agent_id)),
                ("prompt", (None, prompt)),
                ("payment_method", (None, payment_method)),
            ]
            for fname, fobj in files:
                multipart.append(("files", (fname, fobj, "application/octet-stream")))
            resp = self._request("POST", "/orders/multipart", files=multipart)
        else:
            resp = self._request("POST", "/orders/", json={
                "agent_id": agent_id,
                "prompt": prompt,
                "payment_method": payment_method,
            })
        resp.raise_for_status()
        data = resp.json()
        return OrderResponse(
            id=data["id"],
            agent_id=data["agent_id"],
            status=data["status"],
            amount_paid=data["amount_paid"],
            platform_fee=data["platform_fee"],
            created_at=data["created_at"],
        )

    def get_order(self, order_id: str) -> OrderResult:
        """Get the result of a completed order."""
        resp = self._request("GET", f"/orders/{order_id}")
        resp.raise_for_status()
        data = resp.json()
        return OrderResult(
            id=data["id"],
            status=data["status"],
            output_hash=data.get("output_hash"),
            output_text=data.get("output_text"),
            completed_at=data.get("completed_at"),
        )

    def list_agents(self, category: str | None = None, sort: str = "revenue") -> list[AgentListItem]:
        """List available agents."""
        params: dict[str, str] = {"sort": sort}
        if category:
            params["category"] = category
        resp = self._request("GET", "/agents/", params=params)
        resp.raise_for_status()
        return [_parse_agent_list_item(a) for a in resp.json()]

    # --- Publisher methods ---

    def create_agent(
        self,
        name: str,
        ticker: str,
        endpoint_url: str,
        *,
        description: str | None = None,
        category: str | None = None,
        service_price: float = 5.0,
        auth_tier: str = "jwt_default",
        provider: str | None = None,
        provider_api_key: str | None = None,
        manifest: AgentManifest | dict | None = None,
        profile: AgentProfileData | dict | None = None,
    ) -> AgentResponse:
        """Register a new agent on the AUTX marketplace.

        Requires an API key with scope 'agents' and verified email.
        """
        body: dict[str, Any] = {
            "name": name,
            "ticker": ticker,
            "endpoint_url": endpoint_url,
            "service_price": service_price,
            "auth_tier": auth_tier,
        }
        if description is not None:
            body["description"] = description
        if category is not None:
            body["category"] = category
        if provider is not None:
            body["provider"] = provider
        if provider_api_key is not None:
            body["provider_api_key"] = provider_api_key
        if manifest is not None:
            body["manifest"] = manifest.to_dict() if isinstance(manifest, AgentManifest) else manifest
        if profile is not None:
            body["profile"] = profile.to_dict() if isinstance(profile, AgentProfileData) else profile
        resp = self._request("POST", "/agents/", json=body)
        resp.raise_for_status()
        return _parse_agent_response(resp.json())

    def list_my_agents(self) -> list[AgentListItem]:
        """List all agents owned by the authenticated user."""
        resp = self._request("GET", "/agents/mine")
        resp.raise_for_status()
        return [_parse_agent_list_item(a) for a in resp.json()]

    def update_agent(
        self,
        agent_id: str,
        *,
        name: str | None = None,
        description: str | None = None,
        category: str | None = None,
        service_price: float | None = None,
        status: str | None = None,
        endpoint_url: str | None = None,
        auth_tier: str | None = None,
        provider: str | None = None,
        profile: AgentProfileData | dict | None = None,
    ) -> AgentListItem:
        """Update an agent you own. Only provided fields are changed."""
        body: dict[str, Any] = {}
        for key, val in [
            ("name", name), ("description", description), ("category", category),
            ("service_price", service_price), ("status", status),
            ("endpoint_url", endpoint_url), ("auth_tier", auth_tier),
            ("provider", provider),
        ]:
            if val is not None:
                body[key] = val
        if profile is not None:
            body["profile"] = profile.to_dict() if isinstance(profile, AgentProfileData) else profile
        if not body:
            raise ValueError("At least one field must be provided for update")
        resp = self._request("PATCH", f"/agents/{agent_id}", json=body)
        resp.raise_for_status()
        return _parse_agent_list_item(resp.json())

    def update_provider_key(
        self,
        agent_id: str,
        api_key: str,
        provider: str | None = None,
    ) -> AgentListItem:
        """Rotate the provider API key for an agent you own."""
        body: dict[str, Any] = {"api_key": api_key}
        if provider is not None:
            body["provider"] = provider
        resp = self._request("PUT", f"/agents/{agent_id}/provider-key", json=body)
        resp.raise_for_status()
        return _parse_agent_list_item(resp.json())

    def delete_agent(self, agent_id: str) -> None:
        """Delete/delist an agent you own."""
        resp = self._request("DELETE", f"/agents/{agent_id}")
        resp.raise_for_status()


class AutxAsyncClient:
    """Async AUTX API client."""

    def __init__(
        self,
        api_key: str,
        base_url: str = DEFAULT_BASE_URL,
        timeout: int = DEFAULT_TIMEOUT,
        max_retries: int = MAX_RETRIES,
    ):
        if not api_key.startswith("autx_live_"):
            raise ValueError("API key must start with 'autx_live_'")
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.max_retries = max_retries

    def _headers(self) -> dict[str, str]:
        return {"Authorization": f"Bearer {self.api_key}"}

    async def _request(self, method: str, path: str, **kwargs) -> httpx.Response:
        import asyncio
        url = f"{self.base_url}{path}"
        last_exc = None
        for attempt in range(self.max_retries):
            try:
                async with httpx.AsyncClient(timeout=self.timeout) as client:
                    resp = await client.request(method, url, headers=self._headers(), **kwargs)
                    if resp.status_code == 429:
                        retry_after = int(resp.headers.get("Retry-After", "5"))
                        await asyncio.sleep(retry_after)
                        continue
                    return resp
            except httpx.TimeoutException as e:
                last_exc = e
                await asyncio.sleep(RETRY_BACKOFF * (attempt + 1))
            except httpx.HTTPError as e:
                last_exc = e
                break
        raise last_exc or RuntimeError("Request failed after retries")

    # --- Buyer methods ---

    async def proxy(self, ticker_or_id: str, prompt: str | None = None, body: dict | None = None) -> ProxyResponse:
        json_body = body or {"prompt": prompt}
        resp = await self._request("POST", f"/proxy/{ticker_or_id}", json=json_body)
        resp.raise_for_status()
        return ProxyResponse(
            content=resp.content,
            status_code=resp.status_code,
            content_type=resp.headers.get("content-type", ""),
            headers=dict(resp.headers),
            request_id=resp.headers.get("x-autx-request-id", ""),
            latency_ms=int(resp.headers.get("x-autx-latency-ms", "0")),
            agent_ticker=resp.headers.get("x-autx-agent", ""),
        )

    async def order(
        self,
        agent_id: str,
        prompt: str,
        payment_method: str = "stripe",
        files: list[tuple[str, bytes]] | None = None,
    ) -> OrderResponse:
        if files:
            multipart = [
                ("agent_id", (None, agent_id)),
                ("prompt", (None, prompt)),
                ("payment_method", (None, payment_method)),
            ]
            for fname, content in files:
                multipart.append(("files", (fname, content, "application/octet-stream")))
            resp = await self._request("POST", "/orders/multipart", files=multipart)
        else:
            resp = await self._request("POST", "/orders/", json={
                "agent_id": agent_id,
                "prompt": prompt,
                "payment_method": payment_method,
            })
        resp.raise_for_status()
        data = resp.json()
        return OrderResponse(
            id=data["id"],
            agent_id=data["agent_id"],
            status=data["status"],
            amount_paid=data["amount_paid"],
            platform_fee=data["platform_fee"],
            created_at=data["created_at"],
        )

    async def get_order(self, order_id: str) -> OrderResult:
        resp = await self._request("GET", f"/orders/{order_id}")
        resp.raise_for_status()
        data = resp.json()
        return OrderResult(
            id=data["id"],
            status=data["status"],
            output_hash=data.get("output_hash"),
            output_text=data.get("output_text"),
            completed_at=data.get("completed_at"),
        )

    async def list_agents(self, category: str | None = None, sort: str = "revenue") -> list[AgentListItem]:
        params: dict[str, str] = {"sort": sort}
        if category:
            params["category"] = category
        resp = await self._request("GET", "/agents/", params=params)
        resp.raise_for_status()
        return [_parse_agent_list_item(a) for a in resp.json()]

    # --- Publisher methods ---

    async def create_agent(
        self,
        name: str,
        ticker: str,
        endpoint_url: str,
        *,
        description: str | None = None,
        category: str | None = None,
        service_price: float = 5.0,
        auth_tier: str = "jwt_default",
        provider: str | None = None,
        provider_api_key: str | None = None,
        manifest: AgentManifest | dict | None = None,
        profile: AgentProfileData | dict | None = None,
    ) -> AgentResponse:
        """Register a new agent on the AUTX marketplace."""
        body: dict[str, Any] = {
            "name": name,
            "ticker": ticker,
            "endpoint_url": endpoint_url,
            "service_price": service_price,
            "auth_tier": auth_tier,
        }
        if description is not None:
            body["description"] = description
        if category is not None:
            body["category"] = category
        if provider is not None:
            body["provider"] = provider
        if provider_api_key is not None:
            body["provider_api_key"] = provider_api_key
        if manifest is not None:
            body["manifest"] = manifest.to_dict() if isinstance(manifest, AgentManifest) else manifest
        if profile is not None:
            body["profile"] = profile.to_dict() if isinstance(profile, AgentProfileData) else profile
        resp = await self._request("POST", "/agents/", json=body)
        resp.raise_for_status()
        return _parse_agent_response(resp.json())

    async def list_my_agents(self) -> list[AgentListItem]:
        """List all agents owned by the authenticated user."""
        resp = await self._request("GET", "/agents/mine")
        resp.raise_for_status()
        return [_parse_agent_list_item(a) for a in resp.json()]

    async def update_agent(
        self,
        agent_id: str,
        *,
        name: str | None = None,
        description: str | None = None,
        category: str | None = None,
        service_price: float | None = None,
        status: str | None = None,
        endpoint_url: str | None = None,
        auth_tier: str | None = None,
        provider: str | None = None,
        profile: AgentProfileData | dict | None = None,
    ) -> AgentListItem:
        """Update an agent you own. Only provided fields are changed."""
        body: dict[str, Any] = {}
        for key, val in [
            ("name", name), ("description", description), ("category", category),
            ("service_price", service_price), ("status", status),
            ("endpoint_url", endpoint_url), ("auth_tier", auth_tier),
            ("provider", provider),
        ]:
            if val is not None:
                body[key] = val
        if profile is not None:
            body["profile"] = profile.to_dict() if isinstance(profile, AgentProfileData) else profile
        if not body:
            raise ValueError("At least one field must be provided for update")
        resp = await self._request("PATCH", f"/agents/{agent_id}", json=body)
        resp.raise_for_status()
        return _parse_agent_list_item(resp.json())

    async def update_provider_key(
        self,
        agent_id: str,
        api_key: str,
        provider: str | None = None,
    ) -> AgentListItem:
        """Rotate the provider API key for an agent you own."""
        body: dict[str, Any] = {"api_key": api_key}
        if provider is not None:
            body["provider"] = provider
        resp = await self._request("PUT", f"/agents/{agent_id}/provider-key", json=body)
        resp.raise_for_status()
        return _parse_agent_list_item(resp.json())

    async def delete_agent(self, agent_id: str) -> None:
        """Delete/delist an agent you own."""
        resp = await self._request("DELETE", f"/agents/{agent_id}")
        resp.raise_for_status()
