"""
Async telemetry — sends usage metadata to the CostWise backend.

Key design decisions:
- Non-blocking: uses a background thread, never blocks the caller
- Batched: accumulates events and sends in bulk
- Privacy: never sends prompt content, only metadata (tokens, model, cost)
- Fail-safe: silently drops events if backend is unreachable
"""

import atexit
import logging
import threading
import time
from typing import List, Optional

logger = logging.getLogger("costwise_mcp.telemetry")


class TelemetryClient:
    """Non-blocking telemetry client that batches and sends metadata."""

    def __init__(
        self,
        endpoint: str,
        api_key: str,
        batch_size: int = 50,
        flush_interval: float = 30.0,
        timeout: float = 5.0,
    ):
        self._endpoint = endpoint.rstrip("/") + "/api/mcp/ingest"
        self._api_key = api_key
        self._batch_size = batch_size
        self._flush_interval = flush_interval
        self._timeout = timeout
        self._buffer: List[dict] = []
        self._lock = threading.Lock()
        self._running = True
        self._httpx = None

        # Background flush thread
        self._thread = threading.Thread(target=self._flush_loop, daemon=True)
        self._thread.start()

        # Flush on exit
        atexit.register(self.shutdown)

    def _get_http_client(self):
        """Lazy-load httpx to keep the import fast."""
        if self._httpx is None:
            try:
                import httpx
                self._httpx = httpx.Client(timeout=self._timeout)
            except ImportError:
                # httpx not installed — use urllib as fallback
                self._httpx = "urllib"
        return self._httpx

    def track_decision(self, decision_dict: dict, project_id: str = None):
        """Add a decision event to the buffer (non-blocking)."""
        event = {
            "type": "decision",
            "project_id": project_id,
            **decision_dict,
        }
        with self._lock:
            self._buffer.append(event)
            if len(self._buffer) >= self._batch_size:
                self._flush()

    def track_usage(self, usage_dict: dict, project_id: str = None):
        """Add a usage report event to the buffer (non-blocking)."""
        event = {
            "type": "usage",
            "project_id": project_id,
            **usage_dict,
        }
        with self._lock:
            self._buffer.append(event)
            if len(self._buffer) >= self._batch_size:
                self._flush()

    def _flush_loop(self):
        """Background thread that flushes the buffer periodically."""
        while self._running:
            time.sleep(self._flush_interval)
            with self._lock:
                if self._buffer:
                    self._flush()

    def _flush(self):
        """Send buffered events to the backend. Must be called with lock held."""
        if not self._buffer:
            return

        events = self._buffer[:]
        self._buffer.clear()

        # Fire and forget in a separate thread to avoid blocking
        threading.Thread(
            target=self._send, args=(events,), daemon=True
        ).start()

    def _send(self, events: List[dict]):
        """Actually send events to the backend. Retries once on 429 (rate limit)."""
        for attempt in range(2):
            try:
                client = self._get_http_client()
                headers = {
                    "Authorization": f"Bearer {self._api_key}",
                    "Content-Type": "application/json",
                }
                payload = {"events": events}
                status = 0

                if client == "urllib":
                    import json
                    import urllib.request
                    import urllib.error
                    req = urllib.request.Request(
                        self._endpoint,
                        data=json.dumps(payload).encode(),
                        headers=headers,
                        method="POST",
                    )
                    try:
                        resp = urllib.request.urlopen(req, timeout=self._timeout)
                        status = resp.status
                    except urllib.error.HTTPError as e:
                        status = e.code
                else:
                    resp = client.post(self._endpoint, json=payload, headers=headers)
                    status = resp.status_code

                if status == 429:
                    # Rate limited — auto-increase batch size to reduce future requests
                    self._batch_size = min(self._batch_size * 2, 500)
                    logger.debug(
                        "Rate limited (429). Increased batch_size to %d. Retrying in 10s.",
                        self._batch_size,
                    )
                    time.sleep(10)
                    continue  # retry once

                logger.debug("Sent %d events to %s (status=%d)", len(events), self._endpoint, status)
                return  # success

            except Exception as e:
                logger.debug("Telemetry send failed (non-critical): %s", e)
                return  # don't retry on network errors

        # Both attempts were 429 — put events back in buffer (don't lose them)
        with self._lock:
            self._buffer.extend(events[:self._batch_size])
        logger.debug("Rate limited after retry. %d events returned to buffer.", min(len(events), self._batch_size))

    def flush(self):
        """Force flush the buffer (blocking)."""
        events = []
        with self._lock:
            if self._buffer:
                events = self._buffer[:]
                self._buffer.clear()
        if events:
            self._send(events)

    def shutdown(self):
        """Gracefully shutdown: flush remaining events."""
        self._running = False
        self.flush()


class NoopTelemetry:
    """Drop-in replacement when telemetry is disabled."""

    def track_decision(self, *args, **kwargs):
        pass

    def track_usage(self, *args, **kwargs):
        pass

    def flush(self):
        pass

    def shutdown(self):
        pass
