"""Background daemon thread that flushes the buffer to the API."""
from __future__ import annotations

import atexit
import logging
import threading
import time
from typing import TYPE_CHECKING, Any

import httpx

if TYPE_CHECKING:
    from fibinaci._config import FibinaciConfig
    from fibinaci.buffer.memory_buffer import MemoryBuffer

logger = logging.getLogger("fibinaci.buffer")


class BatchProcessor:
    """Flush spans to the Fibinaci API in a background daemon thread.

    Flushes when:
    - Buffer reaches flush_size, OR
    - flush_interval seconds have elapsed, OR
    - stop() is called (final flush), OR
    - Process exits (atexit handler)
    """

    def __init__(self, config: FibinaciConfig, buffer: MemoryBuffer) -> None:
        self._config = config
        self._buffer = buffer
        self._thread: threading.Thread | None = None
        self._stop_event = threading.Event()
        self._registered_atexit = False

    def start(self) -> None:
        if self._thread is not None and self._thread.is_alive():
            return
        self._stop_event.clear()
        self._thread = threading.Thread(target=self._run, daemon=True, name="fibinaci-flush")
        self._thread.start()
        if not self._registered_atexit:
            atexit.register(self.stop)
            self._registered_atexit = True

    def stop(self) -> None:
        self._stop_event.set()
        if self._thread is not None:
            self._thread.join(timeout=5.0)
            self._thread = None
        # Final flush
        self._flush()

    def _run(self) -> None:
        while not self._stop_event.is_set():
            self._stop_event.wait(timeout=self._config.flush_interval)
            if self._buffer.size >= self._config.flush_size or self._stop_event.is_set():
                self._flush()
            elif self._buffer.size > 0:
                self._flush()

    def _flush(self) -> None:
        items = self._buffer.drain(max_items=self._config.flush_size)
        if not items:
            return

        # Separate spans from outcomes
        spans = [i for i in items if i.get("_fibinaci_type") != "outcome"]
        outcomes = [i for i in items if i.get("_fibinaci_type") == "outcome"]

        if spans:
            self._send_spans(spans)
        for o in outcomes:
            self._send_outcome(o)

    def _send_spans(self, spans: list[dict[str, Any]]) -> None:
        if not self._config.api_key:
            logger.warning("FIBINACI_API_KEY is not set. Spans will be buffered but not sent. Dropping %d spans.", len(spans))
            return

        url = f"{self._config.base_url}/v1/spans/batch"
        headers = {
            "Authorization": f"Bearer {self._config.api_key}",
            "Content-Type": "application/json",
        }

        for attempt in range(self._config.max_retries):
            try:
                resp = httpx.post(
                    url,
                    json={"spans": spans},
                    headers=headers,
                    timeout=self._config.timeout,
                )
                if resp.status_code == 202:
                    logger.debug("Flushed %d spans", len(spans))
                    return
                if resp.status_code == 429:
                    retry_after = int(resp.headers.get("Retry-After", "5"))
                    logger.warning("Rate limited, retrying after %ds", retry_after)
                    time.sleep(retry_after)
                    continue
                logger.warning("Flush failed: %d %s", resp.status_code, resp.text[:200])
                return
            except httpx.RequestError as exc:
                logger.debug("Flush network error (attempt %d): %s", attempt + 1, exc)
                time.sleep(min(2 ** attempt, 10))

        logger.warning("Flush failed after %d retries, dropping %d spans", self._config.max_retries, len(spans))

    def _send_outcome(self, outcome: dict[str, Any]) -> None:
        if not self._config.api_key:
            return

        trace_id = outcome.get("trace_id")
        if not trace_id:
            return

        body = {k: v for k, v in outcome.items() if k not in ("_fibinaci_type", "trace_id") and v is not None}
        url = f"{self._config.base_url}/v1/traces/{trace_id}/outcome"
        headers = {
            "Authorization": f"Bearer {self._config.api_key}",
            "Content-Type": "application/json",
        }

        try:
            httpx.post(url, json=body, headers=headers, timeout=self._config.timeout)
        except Exception:
            logger.debug("Failed to send outcome for trace %s", trace_id, exc_info=True)
