"""Background flush thread with bounded deque."""

from __future__ import annotations

import threading
import time
from collections import deque
from typing import TYPE_CHECKING

import structlog

if TYPE_CHECKING:
    from caliper._export import ExportHandler

from caliper._annotation import Annotation
from caliper._record import Record

logger = structlog.get_logger()

BufferItem = Record | Annotation


class RecordBuffer:
    """Thread-safe buffer that drains to an export handler on a background thread."""

    def __init__(
        self,
        handler: ExportHandler,
        flush_interval: float = 2.0,
        batch_size: int = 250,
        max_queue_size: int = 10_000,
    ) -> None:
        self._handler = handler
        self._flush_interval = flush_interval
        self._batch_size = batch_size
        self._queue: deque[BufferItem] = deque(maxlen=max_queue_size)
        self._last_drop_log_time: float = 0.0
        self._stop_event = threading.Event()
        self._thread = threading.Thread(target=self._flush_loop, daemon=True, name="caliper-flush")
        self._thread.start()

    def push(self, item: BufferItem) -> None:
        """Append a record or annotation to the buffer.

        Hot path — only logs when the buffer is full and the throttle cooldown has elapsed.
        """
        if self._queue.maxlen and len(self._queue) >= self._queue.maxlen:
            now = time.monotonic()
            if now - self._last_drop_log_time >= self._flush_interval:
                self._last_drop_log_time = now
                logger.error(
                    "caliper.buffer.dropping_oldest",
                    queue_size=len(self._queue),
                    max_queue_size=self._queue.maxlen,
                )
        self._queue.append(item)

    def shutdown(self, timeout: float = 5.0) -> None:
        """Signal stop, do a final flush, join the thread, close the handler."""
        self._stop_event.set()
        self._thread.join(timeout=timeout)
        # Final flush for any items added after the last loop iteration
        self._flush()
        try:
            self._handler.close()
        except Exception:
            logger.warning("caliper.handler_close_failed", exc_info=True)

    def _flush_loop(self) -> None:
        while not self._stop_event.is_set():
            self._stop_event.wait(timeout=self._flush_interval)
            self._log_utilization()
            self._flush()

    def _log_utilization(self) -> None:
        maxlen = self._queue.maxlen
        if not maxlen:
            return
        size = len(self._queue)
        utilization_pct = round(size / maxlen * 100)
        if utilization_pct > 80:
            logger.warning(
                "caliper.buffer.high_utilization",
                queue_size=size,
                max_queue_size=maxlen,
                utilization_pct=utilization_pct,
            )
        elif utilization_pct > 50:
            logger.info(
                "caliper.buffer.elevated_utilization",
                queue_size=size,
                max_queue_size=maxlen,
                utilization_pct=utilization_pct,
            )

    def _flush(self) -> None:
        """Drain the queue and export in batches, splitting records from annotations."""
        is_first_batch = True
        while self._queue:
            if not is_first_batch:
                time.sleep(0.1)
            is_first_batch = False
            batch: list[BufferItem] = []
            for _ in range(self._batch_size):
                try:
                    batch.append(self._queue.popleft())
                except IndexError:
                    break

            if not batch:
                break

            records = [item for item in batch if isinstance(item, Record)]
            annotations = [item for item in batch if isinstance(item, Annotation)]

            if records:
                try:
                    self._handler.export(records)
                except Exception:
                    logger.warning("caliper.flush_failed", batch_size=len(records), exc_info=True)

            if annotations:
                try:
                    self._handler.export_annotations(annotations)
                except Exception:
                    logger.warning("caliper.flush_annotations_failed", batch_size=len(annotations), exc_info=True)
