from __future__ import annotations

import sys
import threading
from typing import Callable, List

from pulselog.queue import LogQueue
from pulselog.record import LogRecord

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
WORKER_INTERVAL = 0.05  # seconds — default drain / wake interval
WORKER_SHUTDOWN_TIMEOUT = 2.0  # seconds to wait for thread to finish


class BackgroundWorker(threading.Thread):
    """Daemon thread that drains the LogQueue and dispatches batches to handlers.

    The worker sleeps on a ``threading.Event`` so it wakes immediately when new
    records arrive, rather than polling every N milliseconds.  It also wakes
    periodically (every ``interval`` seconds) to flush any records that arrived
    between drain cycles.

    Handler contract:
        Each handler is called as ``handler(batch: List[LogRecord])``.
        If a handler raises an exception the error is written to stderr and the
        worker continues — a bad handler must never crash the pipeline.
    """

    def __init__(
        self,
        queue: LogQueue,
        handlers: List[Callable[[List[LogRecord]], None]],
        interval: float = WORKER_INTERVAL,
    ) -> None:
        super().__init__(daemon=True, name="pulselog-worker")
        self._queue = queue
        self._handlers: List[Callable[[List[LogRecord]], None]] = [
            h for h in handlers if h is not None
        ]
        self._interval = max(interval, 0.01)  # never below 10ms
        self._stop_event = threading.Event()

    # ------------------------------------------------------------------
    # Thread entry-point
    # ------------------------------------------------------------------

    def run(self) -> None:  # noqa: D102
        """Main loop: wait → drain → dispatch.  Runs until shutdown() is called."""
        while not self._stop_event.is_set():
            # Block until a record arrives OR timeout expires.
            self._queue._event.wait(timeout=self._interval)

            batch = self._queue.drain()
            if batch:
                self._dispatch(batch)

        # Final drain after stop signal — flush whatever remains.
        batch = self._queue.drain()
        if batch:
            self._dispatch(batch)

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def shutdown(self, timeout: float = WORKER_SHUTDOWN_TIMEOUT) -> None:
        """Signal the worker to stop and wait for it to finish.

        Args:
            timeout: Maximum seconds to wait for thread termination.
        """
        self._stop_event.set()
        self._queue._event.set()  # wake immediately so the loop can exit
        self.join(timeout=timeout)

    # ------------------------------------------------------------------
    # Private helpers
    # ------------------------------------------------------------------

    def _dispatch(self, batch: List[LogRecord]) -> None:
        """Call each handler with the batch; swallow and log any exceptions."""
        for handler in self._handlers:
            try:
                handler(batch)
            except Exception as exc:  # handler must not crash the worker
                sys.stderr.write(
                    f"[pulselog] Handler {handler!r} raised an exception: {exc}\n"
                )
