"""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:
        """Initialize AuditAdmin (internal -- use ``Primitif().audit`` instead).

        Args:
            client: Pre-configured httpx.Client with auth headers.
            max_retries: Number of automatic retries on transient errors.
        """
        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)``.

        When used as a decorator, automatically logs an audit event for
        each call to the wrapped function, including duration and result.

        Args:
            fn: The function to wrap. Passed automatically when used as
                ``@p.audit`` without parentheses.
            capture_result: If True (default), the function's return value
                is included in the audit event metadata.

        Returns:
            The decorated function (or a decorator if called with options).

        Example:
            >>> @p.audit
            ... def search(query):
            ...     return find_results(query)

            >>> @p.audit(capture_result=False)
            ... def send_email(to, body):
            ...     dispatch(to, body)
        """
        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 AuditEvent.

        Raises:
            AuditValidationError: If the payload is invalid.
            AuditAuthError: If the API key is invalid.

        Example:
            >>> event = p.audit.log(
            ...     "tool.called",
            ...     metadata={"tool": "search", "query": "revenue Q4"},
            ...     resource_type="search",
            ... )
            >>> print(event.id)
        """
        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.

        Args:
            product: Filter by product (e.g. ``"mail"``, ``"approval"``).
            action: Filter by action name.
            source: Filter by event source.
            resource_type: Filter by resource type.
            resource_id: Filter by resource ID.
            status: Filter by status (``"ok"`` or ``"error"``).
            limit: Maximum number of events per page. Defaults to 50.
            cursor: Pagination cursor from a previous response.

        Returns:
            PaginatedList[AuditEvent] supporting iteration and
            ``.auto_paging_iter()``.

        Raises:
            AuditAuthError: If the API key is invalid.

        Example:
            >>> events = p.audit.list(action="tool.called", limit=10)
            >>> for e in events:
            ...     print(e.action, e.status, e.duration_ms)
        """
        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.

        Args:
            event_id: The audit event ID.

        Returns:
            The AuditEvent.

        Raises:
            AuditNotFoundError: If the event does not exist.
            AuditAuthError: If the API key is invalid.

        Example:
            >>> event = p.audit.get("evt_abc123")
            >>> print(event.action, event.metadata)
        """
        data = self._request("GET", f"/v1/events/{event_id}")
        return AuditEvent(**data)
