"""Chox Python SDK — zero-dependency client built on urllib."""

from __future__ import annotations

import json
import socket
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timezone
from typing import Any, Optional

from .errors import ChoxError, ChoxNetworkError
from .models import (
    ActionStats,
    Caller,
    EvaluateResponse,
    GatewayConfig,
    HourlyStats,
    Integration,
    Project,
    RequestLog,
    TotalCount,
    _from_dict,
)

_DEFAULT_TIMEOUT = 5.0  # seconds


def _fail_open_verdict() -> EvaluateResponse:
    return EvaluateResponse(
        request_id="chox_fail_open",
        verdict="allow",
        action_type="unknown",
        risk_score=0.0,
        reason="Chox unreachable \u2014 fail-open",
        evaluated_at=datetime.now(timezone.utc).isoformat(),
    )


# ---------------------------------------------------------------------------
# Sub-resource classes
# ---------------------------------------------------------------------------


class _ProjectsResource:
    def __init__(self, request: Any) -> None:
        self._request = request

    def create(self, *, name: str, slug: str) -> Project:
        """Create a new project (no auth required). Returns the raw admin key once."""
        data = self._request("POST", "/api/v1/projects", body={"name": name, "slug": slug}, auth=False)
        return _from_dict(Project, data)

    def get(self) -> Project:
        """Get the authenticated project."""
        return _from_dict(Project, self._request("GET", "/api/v1/project"))


class _CallersResource:
    def __init__(self, request: Any) -> None:
        self._request = request

    def create(self, *, name: str, description: str = "") -> Caller:
        """Register a new AI caller. Returns the raw token once."""
        data = self._request("POST", "/api/v1/callers", body={"name": name, "description": description})
        return _from_dict(Caller, data)

    def list(self) -> list[Caller]:
        """List all callers for the authenticated project. Tokens are masked."""
        return [_from_dict(Caller, c) for c in self._request("GET", "/api/v1/callers")]

    def delete(self, id: str) -> None:
        """Deactivate / delete a caller."""
        self._request("DELETE", f"/api/v1/callers/{urllib.parse.quote(id, safe='')}")


class _IntegrationsResource:
    def __init__(self, request: Any) -> None:
        self._request = request

    def create(self, *, name: str, integration_type: str, destination_url: str) -> Integration:
        """Register an external service integration."""
        data = self._request(
            "POST",
            "/api/v1/integrations",
            body={"name": name, "integration_type": integration_type, "destination_url": destination_url},
        )
        return _from_dict(Integration, data)

    def list(self) -> list[Integration]:
        """List all integrations for the authenticated project."""
        return [_from_dict(Integration, i) for i in self._request("GET", "/api/v1/integrations")]

    def delete(self, id: str) -> None:
        """Remove an integration."""
        self._request("DELETE", f"/api/v1/integrations/{urllib.parse.quote(id, safe='')}")


class _LogsResource:
    def __init__(self, request: Any) -> None:
        self._request = request

    def list(
        self,
        *,
        caller_id: Optional[str] = None,
        integration: Optional[str] = None,
        action_type: Optional[str] = None,
        start: Optional[str] = None,
        end: Optional[str] = None,
        limit: Optional[int] = None,
        offset: Optional[int] = None,
    ) -> list[RequestLog]:
        """Query request logs with optional filters."""
        params = _build_params(
            caller_id=caller_id,
            integration=integration,
            action_type=action_type,
            start=start,
            end=end,
            limit=limit,
            offset=offset,
        )
        qs = urllib.parse.urlencode(params)
        path = f"/api/v1/logs?{qs}" if qs else "/api/v1/logs"
        return [_from_dict(RequestLog, log) for log in self._request("GET", path)]

    def get(self, id: str) -> RequestLog:
        """Get a single log entry by ID."""
        return _from_dict(RequestLog, self._request("GET", f"/api/v1/logs/{urllib.parse.quote(id, safe='')}"))


class _StatsResource:
    def __init__(self, request: Any) -> None:
        self._request = request

    def actions(self, *, start: Optional[str] = None, end: Optional[str] = None) -> list[ActionStats]:
        """Action type breakdown."""
        qs = urllib.parse.urlencode(_build_params(start=start, end=end))
        path = f"/api/v1/stats/actions?{qs}" if qs else "/api/v1/stats/actions"
        return [_from_dict(ActionStats, s) for s in self._request("GET", path)]

    def hourly(self, *, start: Optional[str] = None, end: Optional[str] = None) -> list[HourlyStats]:
        """Hourly activity counts."""
        qs = urllib.parse.urlencode(_build_params(start=start, end=end))
        path = f"/api/v1/stats/hourly?{qs}" if qs else "/api/v1/stats/hourly"
        return [_from_dict(HourlyStats, s) for s in self._request("GET", path)]

    def integrations(self, *, start: Optional[str] = None, end: Optional[str] = None) -> dict[str, int]:
        """Per-integration traffic counts."""
        qs = urllib.parse.urlencode(_build_params(start=start, end=end))
        path = f"/api/v1/stats/integrations?{qs}" if qs else "/api/v1/stats/integrations"
        return self._request("GET", path)

    def total(self) -> TotalCount:
        """Total request count."""
        return _from_dict(TotalCount, self._request("GET", "/api/v1/stats/total"))


# ---------------------------------------------------------------------------
# Main client
# ---------------------------------------------------------------------------


class ChoxClient:
    """Zero-dependency Python client for the Chox API.

    Usage::

        from chox import ChoxClient

        chox = ChoxClient(base_url="https://chox.example.com", token="chox_token_...")

        verdict = chox.evaluate(tool="stripe.create_charge", arguments={"amount": 5000})
        if verdict.verdict == "allow":
            ...  # proceed
    """

    def __init__(
        self,
        *,
        base_url: str,
        token: str,
        fail_open: bool = True,
        timeout: float = _DEFAULT_TIMEOUT,
    ) -> None:
        self._base_url = base_url.rstrip("/")
        self._token = token
        self._fail_open = fail_open
        self._timeout = timeout

        self.projects = _ProjectsResource(self._request)
        self.callers = _CallersResource(self._request)
        self.integrations = _IntegrationsResource(self._request)
        self.logs = _LogsResource(self._request)
        self.stats = _StatsResource(self._request)

    # -----------------------------------------------------------------------
    # Core: evaluate a tool call
    # -----------------------------------------------------------------------

    def evaluate(
        self,
        *,
        tool: str,
        arguments: Optional[dict[str, Any]] = None,
        caller_token: Optional[str] = None,
        context: Optional[dict[str, Any]] = None,
    ) -> EvaluateResponse:
        """Evaluate a tool call before executing it.

        When *fail_open* is ``True`` (the default), network errors and timeouts
        return an allow verdict so your agent is never blocked by Chox being
        unreachable.
        """
        body: dict[str, Any] = {"tool": tool}
        if arguments is not None:
            body["arguments"] = arguments
        if caller_token is not None:
            body["caller_token"] = caller_token
        if context is not None:
            body["context"] = context

        try:
            data = self._request("POST", "/api/v1/evaluate", body=body)
            return _from_dict(EvaluateResponse, data)
        except (ChoxError, ChoxNetworkError):
            if self._fail_open:
                return _fail_open_verdict()
            raise

    # -----------------------------------------------------------------------
    # Public config (no auth)
    # -----------------------------------------------------------------------

    def config(self) -> GatewayConfig:
        """Fetch the public gateway configuration."""
        return _from_dict(GatewayConfig, self._request("GET", "/api/v1/config", auth=False))

    # -----------------------------------------------------------------------
    # Internal HTTP plumbing
    # -----------------------------------------------------------------------

    def _request(
        self,
        method: str,
        path: str,
        *,
        body: Optional[dict] = None,
        auth: bool = True,
    ) -> Any:
        url = f"{self._base_url}{path}"
        headers: dict[str, str] = {}

        if auth:
            headers["Authorization"] = f"Bearer {self._token}"

        data: Optional[bytes] = None
        if body is not None:
            headers["Content-Type"] = "application/json"
            data = json.dumps(body).encode()

        req = urllib.request.Request(url, data=data, headers=headers, method=method)

        try:
            resp = urllib.request.urlopen(req, timeout=self._timeout)
        except urllib.error.HTTPError as exc:
            raise ChoxError(exc.code, exc.read().decode()) from exc
        except (urllib.error.URLError, socket.timeout, OSError) as exc:
            raise ChoxNetworkError(str(exc), cause=exc) from exc

        # 204 No Content (e.g. DELETE)
        if resp.status == 204:
            return None

        return json.loads(resp.read().decode())


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _build_params(**kwargs: Any) -> dict[str, str]:
    return {k: str(v) for k, v in kwargs.items() if v is not None}
