"""Audit — unified agent activity log."""

from __future__ import annotations

import functools
import time
from typing import Any, Callable, Literal

import httpx

from primitif._models import PaginatedList
from primitif._transport import (
    configure_logging,
    request_with_retries,
)
from primitif.audit.exceptions import (
    AuditAuthError,
    AuditError,
    AuditNetworkError,
    AuditNotFoundError,
    AuditRateLimitError,
    AuditServerError,
    AuditValidationError,
)
from primitif.audit.models import AuditEvent

logger = configure_logging("audit")

_EXC_MAP = {
    401: AuditAuthError,
    404: AuditNotFoundError,
    422: AuditValidationError,
    429: AuditRateLimitError,
}


class AuditAdmin:
    """Audit operations — log events, list events, and decorator.

    Not instantiated directly. Access via ``Primitif().audit``.

    Usage::

        # Manual logging
        p.audit.log("tool.called", metadata={"tool": "search"})

        # Decorator
        @p.audit
        def my_tool(query):
            return search(query)

        # Decorator with options
        @p.audit(capture_result=False)
        def my_tool(query):
            return search(query)
    """

    def __init__(self, client: httpx.Client, max_retries: int) -> None:
        self._client = client
        self._max_retries = max_retries

    def _request(self, method: str, path: str, **kwargs) -> dict:
        return request_with_retries(
            self._client,
            method,
            path,
            self._max_retries,
            exc_map=_EXC_MAP,
            server_exc=AuditServerError,
            base_exc=AuditError,
            connection_exc=AuditNetworkError,
            logger=logger,
            **kwargs,
        )

    # ------------------------------------------------------------------
    # Decorator: @p.audit or @p.audit(capture_result=False)
    # ------------------------------------------------------------------

    def __call__(self, fn: Callable | None = None, *, capture_result: bool = True) -> Any:
        """Use as ``@p.audit`` or ``@p.audit(capture_result=False)``."""
        if fn is not None:
            return self._wrap(fn, capture_result=True)
        return lambda f: self._wrap(f, capture_result=capture_result)

    def _wrap(self, fn: Callable, capture_result: bool) -> Callable:
        @functools.wraps(fn)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            start = time.monotonic()
            try:
                result = fn(*args, **kwargs)
                elapsed = int((time.monotonic() - start) * 1000)
                meta = {"result": result} if capture_result and result is not None else None
                self.log(
                    fn.__name__,
                    status="ok",
                    duration_ms=elapsed,
                    metadata=meta,
                )
                return result
            except Exception as exc:
                elapsed = int((time.monotonic() - start) * 1000)
                self.log(
                    fn.__name__,
                    status="error",
                    error=type(exc).__name__,
                    duration_ms=elapsed,
                )
                raise

        return wrapper

    # ------------------------------------------------------------------
    # Manual logging
    # ------------------------------------------------------------------

    def log(
        self,
        action: str,
        *,
        metadata: dict[str, Any] | None = None,
        status: str = "ok",
        error: str | None = None,
        duration_ms: int | None = None,
        resource_type: str | None = None,
        resource_id: str | None = None,
    ) -> AuditEvent:
        """Log a custom audit event.

        Args:
            action: What happened (e.g. ``"tool.called"``, ``"search.executed"``).
            metadata: Arbitrary JSON metadata.
            status: ``"ok"`` or ``"error"``.
            error: Error message if status is ``"error"``.
            duration_ms: How long the operation took.
            resource_type: Type of resource involved.
            resource_id: ID of resource involved.

        Returns:
            The created :class:`AuditEvent`.
        """
        payload: dict[str, Any] = {"action": action}
        if metadata is not None:
            payload["metadata"] = metadata
        if status != "ok":
            payload["status"] = status
        if error is not None:
            payload["error"] = error
        if duration_ms is not None:
            payload["duration_ms"] = duration_ms
        if resource_type is not None:
            payload["resource_type"] = resource_type
        if resource_id is not None:
            payload["resource_id"] = resource_id

        data = self._request("POST", "/v1/events", json=payload)
        return AuditEvent(**data)

    # ------------------------------------------------------------------
    # List / get
    # ------------------------------------------------------------------

    def list(
        self,
        *,
        product: str | None = None,
        action: str | None = None,
        source: str | None = None,
        resource_type: str | None = None,
        resource_id: str | None = None,
        status: str | None = None,
        limit: int = 50,
        cursor: str | None = None,
    ) -> PaginatedList[AuditEvent]:
        """List audit events, optionally filtered."""
        params: dict[str, Any] = {"limit": limit}
        if product is not None:
            params["product"] = product
        if action is not None:
            params["action"] = action
        if source is not None:
            params["source"] = source
        if resource_type is not None:
            params["resource_type"] = resource_type
        if resource_id is not None:
            params["resource_id"] = resource_id
        if status is not None:
            params["status"] = status
        if cursor is not None:
            params["cursor"] = cursor

        data = self._request("GET", "/v1/events", params=params)
        raw = data.get("items") or []
        items = [AuditEvent(**r) for r in raw]
        next_cursor = data.get("cursor")
        result = PaginatedList(
            items=items, cursor=next_cursor, has_more=data.get("has_more", False),
        )
        result._fetch_fn = self.list
        result._fetch_kwargs = {
            "product": product, "action": action, "source": source,
            "resource_type": resource_type, "resource_id": resource_id,
            "status": status, "limit": limit,
        }
        return result

    def get(self, event_id: str) -> AuditEvent:
        """Get a single audit event by ID."""
        data = self._request("GET", f"/v1/events/{event_id}")
        return AuditEvent(**data)
