"""Log queue for batching and async sending."""

from __future__ import annotations

import json
import logging
import queue
import threading
import time
from typing import Any, Optional

import httpx

logger = logging.getLogger("aitracer")


class LogQueue:
    """
    Queue for batching and asynchronously sending logs to AITracer API.

    Features:
    - Batches logs for efficient sending
    - Background thread for async sending
    - Automatic flush on interval
    - Graceful shutdown with flush
    - Sync mode for serverless environments
    """

    def __init__(
        self,
        api_key: str,
        base_url: str,
        batch_size: int = 10,
        flush_interval: float = 5.0,
        sync: bool = False,
        max_queue_size: int = 10000,
        timeout: float = 30.0,
    ):
        """
        Initialize the log queue.

        Args:
            api_key: AITracer API key.
            base_url: AITracer API base URL.
            batch_size: Number of logs to batch before sending.
            flush_interval: Seconds between automatic flushes.
            sync: If True, send logs synchronously.
            max_queue_size: Maximum queue size before dropping logs.
            timeout: HTTP request timeout in seconds.
        """
        self.api_key = api_key
        self.base_url = base_url
        self.batch_size = batch_size
        self.flush_interval = flush_interval
        self.sync = sync
        self.max_queue_size = max_queue_size
        self.timeout = timeout

        self._queue: queue.Queue[dict[str, Any]] = queue.Queue(maxsize=max_queue_size)
        self._shutdown = threading.Event()
        self._worker_thread: Optional[threading.Thread] = None

        # HTTP client
        self._client = httpx.Client(
            base_url=base_url,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
            },
            timeout=timeout,
        )

        # Start background worker if async mode
        if not sync:
            self._start_worker()

    def _start_worker(self) -> None:
        """Start the background worker thread."""
        self._worker_thread = threading.Thread(
            target=self._worker_loop,
            daemon=True,
            name="aitracer-worker",
        )
        self._worker_thread.start()

    def _worker_loop(self) -> None:
        """Background worker loop for processing queued logs."""
        batch: list[dict[str, Any]] = []
        last_flush = time.time()

        while not self._shutdown.is_set():
            try:
                # Try to get an item with timeout
                try:
                    item = self._queue.get(timeout=0.1)
                    batch.append(item)
                    self._queue.task_done()
                except queue.Empty:
                    pass

                # Check if we should flush
                should_flush = (
                    len(batch) >= self.batch_size
                    or (batch and time.time() - last_flush >= self.flush_interval)
                )

                if should_flush and batch:
                    self._send_batch(batch)
                    batch = []
                    last_flush = time.time()

            except Exception as e:
                logger.exception("Error in worker loop: %s", e)
                time.sleep(1)

        # Final flush on shutdown
        if batch:
            self._send_batch(batch)

        # Drain remaining items
        while not self._queue.empty():
            try:
                item = self._queue.get_nowait()
                batch.append(item)
                self._queue.task_done()
                if len(batch) >= self.batch_size:
                    self._send_batch(batch)
                    batch = []
            except queue.Empty:
                break

        if batch:
            self._send_batch(batch)

    def _send_batch(self, batch: list[dict[str, Any]]) -> None:
        """Send a batch of logs to the API."""
        if not batch:
            return

        try:
            response = self._client.post(
                "/api/v1/logs/batch/",
                json={"logs": batch},
            )
            response.raise_for_status()
            logger.debug("Sent %d logs successfully", len(batch))
        except httpx.HTTPStatusError as e:
            logger.error(
                "Failed to send logs: HTTP %d - %s",
                e.response.status_code,
                e.response.text,
            )
        except httpx.RequestError as e:
            logger.error("Failed to send logs: %s", e)
        except Exception as e:
            logger.exception("Unexpected error sending logs: %s", e)

    def _send_single(self, log: dict[str, Any]) -> None:
        """Send a single log synchronously."""
        try:
            response = self._client.post(
                "/api/v1/logs/",
                json=log,
            )
            response.raise_for_status()
            logger.debug("Sent log successfully")
        except httpx.HTTPStatusError as e:
            logger.error(
                "Failed to send log: HTTP %d - %s",
                e.response.status_code,
                e.response.text,
            )
        except httpx.RequestError as e:
            logger.error("Failed to send log: %s", e)
        except Exception as e:
            logger.exception("Unexpected error sending log: %s", e)

    def add(self, log: dict[str, Any]) -> None:
        """
        Add a log to the queue.

        Args:
            log: Log entry dictionary.
        """
        if self.sync:
            # Sync mode: send immediately
            self._send_single(log)
        else:
            # Async mode: add to queue
            try:
                self._queue.put_nowait(log)
            except queue.Full:
                logger.warning("Log queue is full, dropping log")

    def flush(self) -> None:
        """Flush all pending logs immediately."""
        if self.sync:
            return  # Nothing to flush in sync mode

        # Collect all items
        batch: list[dict[str, Any]] = []
        while not self._queue.empty():
            try:
                item = self._queue.get_nowait()
                batch.append(item)
                self._queue.task_done()
            except queue.Empty:
                break

        # Send in batches
        for i in range(0, len(batch), self.batch_size):
            self._send_batch(batch[i : i + self.batch_size])

    def shutdown(self) -> None:
        """Shutdown the queue and wait for pending logs."""
        self._shutdown.set()
        if self._worker_thread:
            self._worker_thread.join(timeout=10.0)
        self._client.close()
