import asyncio
import functools
import logging
import time
import uuid
from typing import Optional

from agentmetrics.http_client import HttpClient

logger = logging.getLogger("agentmetrics")


class Sentinel:
    """
    Core tracking class. Use the module-level `sentinel` singleton.

    Usage:
        from agentmetrics import sentinel
        sentinel.configure(api_key="am_...")

        @sentinel.track(agent_id="my_agent")
        def my_agent(task):
            return result
    """

    def __init__(self):
        self._api_key: Optional[str] = None
        self._client: Optional[HttpClient] = None

    def configure(
        self,
        api_key: str,
        base_url: str = "https://api.agentmetrics.dev/v1",
    ) -> None:
        """Call once at app startup before using @track."""
        self._api_key = api_key
        self._client = HttpClient(api_key=api_key, base_url=base_url)

    @property
    def is_configured(self) -> bool:
        return self._api_key is not None

    def track(self, agent_id: str):
        """
        Decorator that wraps a function (sync or async) and sends
        execution metrics to AgentMetrics. Graceful no-op if not configured.

        Args:
            agent_id: Unique identifier for this agent in the dashboard.

        Example:
            @sentinel.track(agent_id="customer_support")
            def handle_ticket(ticket):
                ...
        """
        def decorator(func):
            if asyncio.iscoroutinefunction(func):
                @functools.wraps(func)
                async def async_wrapper(*args, **kwargs):
                    if not self.is_configured:
                        return await func(*args, **kwargs)
                    return await self._run_async(func, agent_id, args, kwargs)
                return async_wrapper
            else:
                @functools.wraps(func)
                def sync_wrapper(*args, **kwargs):
                    if not self.is_configured:
                        return func(*args, **kwargs)
                    return self._run_sync(func, agent_id, args, kwargs)
                return sync_wrapper
        return decorator

    def _run_sync(self, func, agent_id: str, args, kwargs):
        trace_id = str(uuid.uuid4())
        start = time.monotonic()
        status = "success"
        error_msg = None
        try:
            result = func(*args, **kwargs)
            return result
        except Exception as exc:
            status = "failed"
            error_msg = str(exc)
            raise
        finally:
            duration_ms = (time.monotonic() - start) * 1000
            self._send(trace_id, agent_id, status, duration_ms, error_msg)

    async def _run_async(self, func, agent_id: str, args, kwargs):
        trace_id = str(uuid.uuid4())
        start = time.monotonic()
        status = "success"
        error_msg = None
        try:
            result = await func(*args, **kwargs)
            return result
        except Exception as exc:
            status = "failed"
            error_msg = str(exc)
            raise
        finally:
            duration_ms = (time.monotonic() - start) * 1000
            self._send(trace_id, agent_id, status, duration_ms, error_msg)

    def _send(
        self,
        trace_id: str,
        agent_id: str,
        status: str,
        duration_ms: float,
        error_msg: Optional[str],
        cost_usd: float = 0.0,
        model: Optional[str] = None,
        input_tokens: Optional[int] = None,
        output_tokens: Optional[int] = None,
    ) -> None:
        if not self._client:
            return
        payload = {
            "trace_id": trace_id,
            "agent_id": agent_id,
            "status": status,
            "duration_ms": duration_ms,
            "cost_usd": cost_usd,
            "error": error_msg,
        }
        if model:
            payload["model"] = model
        if input_tokens is not None:
            payload["input_tokens"] = input_tokens
        if output_tokens is not None:
            payload["output_tokens"] = output_tokens

        self._client.fire_and_forget(payload)

    def flush(self, timeout: float = 10.0) -> None:
        """Wait for all pending events to be sent. Call before process exit."""
        if self._client:
            self._client.flush(timeout=timeout)


# Module-level singleton
sentinel = Sentinel()
