import atexit
import json
import logging
import sys
import threading
import time
from collections import deque

import httpx

from .event import CostEvent

logger = logging.getLogger("agentcost")

DEFAULT_API_URL = "https://agentmeter.com/api/v1"
BATCH_SIZE = 50
FLUSH_INTERVAL = 5.0  # seconds
MAX_RETRIES = 3
RETRY_BACKOFF_BASE = 1.0  # seconds — retries at 1s, 2s, 4s


class EventClient:
    """HTTP client that batches and sends cost events."""

    def __init__(
        self,
        api_key: str,
        api_url: str = DEFAULT_API_URL,
        debug: bool = False,
    ):
        self._api_key = api_key
        self._api_url = api_url.rstrip("/")
        self._debug = debug
        self._queue: deque[dict] = deque()
        self._lock = threading.Lock()
        self._running = True

        if not debug:
            # Background flush thread
            self._flush_thread = threading.Thread(
                target=self._flush_loop, daemon=True
            )
            self._flush_thread.start()

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

    def enqueue(self, event: CostEvent) -> None:
        """Add an event to the send queue."""
        if self._debug:
            self._print_debug(event)
            return

        with self._lock:
            self._queue.append(event.to_dict())

        # Flush if batch is full
        if len(self._queue) >= BATCH_SIZE:
            self._flush()

    def _print_debug(self, event: CostEvent) -> None:
        """Print event to stderr in debug mode."""
        parts = [
            f"[agentcost] {event.event_type}",
            f"agent={event.agent_id}",
            f"component={event.component_name}",
            f"cost=${event.cost_usd:.6f}",
        ]
        if event.run_id:
            parts.append(f"run={event.run_id[:8]}")
        if event.tokens_in is not None:
            parts.append(f"tokens_in={event.tokens_in}")
        if event.tokens_out is not None:
            parts.append(f"tokens_out={event.tokens_out}")
        if event.duration_ms is not None:
            parts.append(f"duration={event.duration_ms}ms")
        print(" | ".join(parts), file=sys.stderr)

    def _flush_loop(self) -> None:
        """Background thread that flushes events periodically."""
        while self._running:
            time.sleep(FLUSH_INTERVAL)
            self._flush()

    def _flush(self) -> None:
        """Send all queued events to the API with retries."""
        with self._lock:
            if not self._queue:
                return
            events = list(self._queue)
            self._queue.clear()

        for attempt in range(MAX_RETRIES):
            try:
                response = httpx.post(
                    f"{self._api_url}/events",
                    json={"events": events},
                    headers={
                        "Authorization": f"Bearer {self._api_key}",
                        "Content-Type": "application/json",
                    },
                    timeout=10.0,
                )

                if response.status_code == 200:
                    data = response.json()
                    if data.get("warnings"):
                        for w in data["warnings"]:
                            logger.warning(f"AgentCost budget warning: {w}")
                    return  # success
                elif response.status_code == 401:
                    logger.error("AgentCost: Invalid API key")
                    return  # don't retry auth errors
                elif response.status_code >= 500:
                    # Server error — retry
                    logger.warning(
                        f"AgentCost: Server error {response.status_code}, "
                        f"attempt {attempt + 1}/{MAX_RETRIES}"
                    )
                else:
                    # Client error (4xx) — don't retry
                    logger.warning(
                        f"AgentCost: Failed to send events "
                        f"(status {response.status_code})"
                    )
                    return
            except (httpx.TimeoutException, httpx.ConnectError) as e:
                logger.warning(
                    f"AgentCost: Network error, "
                    f"attempt {attempt + 1}/{MAX_RETRIES}: {e}"
                )
            except Exception as e:
                logger.warning(f"AgentCost: Failed to send events: {e}")
                return  # unexpected error — don't retry

            # Exponential backoff before next retry
            if attempt < MAX_RETRIES - 1:
                time.sleep(RETRY_BACKOFF_BASE * (2 ** attempt))

        logger.error(
            f"AgentCost: Dropped {len(events)} events after {MAX_RETRIES} retries"
        )

    def shutdown(self) -> None:
        """Stop the flush thread and send remaining events."""
        self._running = False
        if not self._debug:
            self._flush()
