"""
Meter — the main developer-facing interface.

Usage:
    from climeter import meter

    # Configure once (or via CLIMETER_API_KEY env var)
    meter.configure(api_key="clm_xxx")

    # Decorator
    @meter.track(price=0.01)
    def search(query: str):
        ...

    # Context manager
    with meter.track_usage("search", price=0.01):
        result = do_search(query)

    # Async decorator
    @meter.track(price=0.01)
    async def search(query: str):
        ...
"""

import asyncio
import contextlib
import datetime
import functools
import os
import threading
import time
from typing import Any, Callable, Dict, Iterator, Optional, TypeVar

from .client import ClimeterClient, DEFAULT_BASE_URL

F = TypeVar("F", bound=Callable[..., Any])

_NO_KEY_WARNING_SHOWN = False
_WARNING_LOCK = threading.Lock()


def _warn_once(message: str) -> None:
    global _NO_KEY_WARNING_SHOWN
    with _WARNING_LOCK:
        if not _NO_KEY_WARNING_SHOWN:
            print(f"[climeter] WARNING: {message}", flush=True)
            _NO_KEY_WARNING_SHOWN = True


def _utc_now() -> str:
    return datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")


class Meter:
    """
    Thread-safe meter interface. Lazily initialises from environment variables.
    All operations are fire-and-forget — they never block the caller.
    """

    def __init__(self) -> None:
        self._client: Optional[ClimeterClient] = None
        self._tool_slug: str = ""
        self._lock = threading.Lock()

    # ------------------------------------------------------------------
    # Configuration
    # ------------------------------------------------------------------

    def configure(
        self,
        api_key: str,
        api_url: str = DEFAULT_BASE_URL,
        tool_slug: str = "",
    ) -> None:
        """
        Configure the meter globally.

        Args:
            api_key:   Your Climeter API key (clm_xxx)
            api_url:   Override the API base URL (default: https://api.climeter.auvh.ai)
            tool_slug: Optional slug identifying this tool/CLI across events
        """
        with self._lock:
            self._client = ClimeterClient(api_key=api_key, base_url=api_url)
            self._tool_slug = tool_slug

    # Keep private alias for internal use
    _configure = configure

    def _get_client(self) -> Optional[ClimeterClient]:
        """Return initialised client, or None (with a one-time warning) if unconfigured."""
        if self._client is None:
            with self._lock:
                if self._client is None:
                    api_key = os.environ.get("CLIMETER_API_KEY", "")
                    api_url = os.environ.get("CLIMETER_API_URL", DEFAULT_BASE_URL)
                    tool_slug = os.environ.get("CLIMETER_TOOL_SLUG", "")
                    if not api_key:
                        _warn_once(
                            "CLIMETER_API_KEY is not set. Metering is disabled. "
                            "Set the env var or call meter.configure(api_key='...')."
                        )
                        return None
                    self._client = ClimeterClient(api_key=api_key, base_url=api_url)
                    self._tool_slug = tool_slug
        return self._client

    # ------------------------------------------------------------------
    # Core record
    # ------------------------------------------------------------------

    def record(
        self,
        event_name: str,
        *,
        price: float = 0.0,
        tool_slug: str = "",
        metadata: Optional[Dict[str, Any]] = None,
    ) -> None:
        """
        Fire-and-forget: enqueue a single usage event.
        Returns immediately — never blocks.
        """
        client = self._get_client()
        if client is None:
            return  # no-op when unconfigured

        payload: Dict[str, Any] = {
            "tool_slug": tool_slug or self._tool_slug or event_name,
            "event_name": event_name,
            "price_usd": float(price),
            "metadata": metadata or {},
            "timestamp": _utc_now(),
        }
        client.enqueue(payload)

    # ------------------------------------------------------------------
    # Decorator
    # ------------------------------------------------------------------

    def track(
        self,
        price: float = 0.0,
        name: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> Callable[[F], F]:
        """
        Decorator that automatically records a usage event.

        Args:
            price:    Cost per execution in USD
            name:     Override event name (defaults to function name)
            metadata: Extra key-value pairs attached to the event

        Example::

            @meter.track(price=0.01)
            def search(query: str):
                ...
        """

        def decorator(fn: F) -> F:
            event_name = name or fn.__name__

            if asyncio.iscoroutinefunction(fn):
                @functools.wraps(fn)
                async def async_wrapper(*args, **kwargs):
                    try:
                        return await fn(*args, **kwargs)
                    finally:
                        self.record(event_name, price=price, metadata=metadata)

                return async_wrapper  # type: ignore
            else:
                @functools.wraps(fn)
                def sync_wrapper(*args, **kwargs):
                    try:
                        return fn(*args, **kwargs)
                    finally:
                        self.record(event_name, price=price, metadata=metadata)

                return sync_wrapper  # type: ignore

        return decorator

    # ------------------------------------------------------------------
    # Context manager
    # ------------------------------------------------------------------

    @contextlib.contextmanager
    def track_usage(
        self,
        event_name: str,
        price: float = 0.0,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> Iterator[None]:
        """
        Context manager that records a usage event on exit.

        Example::

            with meter.track_usage("search", price=0.01):
                result = do_search(query)
        """
        try:
            yield
        finally:
            self.record(event_name, price=price, metadata=metadata)

    # ------------------------------------------------------------------
    # Lifecycle helpers
    # ------------------------------------------------------------------

    def flush(self) -> None:
        """Manually flush pending events. Call before process exit if needed."""
        if self._client:
            self._client.flush()

    def shutdown(self) -> None:
        """Graceful shutdown — flushes then stops the background thread."""
        if self._client:
            self._client.shutdown()

    # ------------------------------------------------------------------
    # Async context manager
    # ------------------------------------------------------------------

    @contextlib.asynccontextmanager
    async def track_usage_async(
        self,
        event_name: str,
        price: float = 0.0,
        metadata: Optional[Dict[str, Any]] = None,
    ):
        """
        Async context manager version of track_usage.

        Example::

            async with meter.track_usage_async("search", price=0.01):
                result = await do_async_search(query)
        """
        try:
            yield
        finally:
            self.record(event_name, price=price, metadata=metadata)

    # ------------------------------------------------------------------
    # Diagnostics
    # ------------------------------------------------------------------

    def status(self) -> dict:
        """Return current meter state for debugging."""
        client = self._get_client()
        return {
            "configured": client is not None,
            "tool_slug": self._tool_slug,
            "queue_size": client.queue_size() if client else 0,
        }
