"""Event buffer with time-based and size-based flushing."""

from __future__ import annotations

import asyncio
import sys
import threading
from collections.abc import Callable, Coroutine
from typing import Any

from .types import QueuedEvent

FlushFn = Callable[[list[QueuedEvent]], Coroutine[Any, Any, None]]


class EventBuffer:
    """Thread-safe event buffer with periodic and size-based flushing."""

    def __init__(
        self,
        flush_fn: FlushFn,
        flush_interval: float = 1.0,
        batch_size: int = 100,
    ) -> None:
        self._flush_fn = flush_fn
        self._flush_interval = flush_interval
        self._batch_size = batch_size
        self._queue: list[QueuedEvent] = []
        self._lock = threading.Lock()
        self._timer: threading.Timer | None = None
        self._loop: asyncio.AbstractEventLoop | None = None

    def start(self) -> None:
        """Start the periodic flush timer. Must be called from an async context."""
        try:
            self._loop = asyncio.get_running_loop()
        except RuntimeError:
            self._loop = None
        self._schedule_flush()

    def stop(self) -> None:
        """Stop the periodic flush timer."""
        if self._timer is not None:
            self._timer.cancel()
            self._timer = None

    def push(self, event: QueuedEvent) -> None:
        """Add an event to the buffer. Triggers flush if batch size reached."""
        batch: list[QueuedEvent] | None = None
        with self._lock:
            self._queue.append(event)
            if len(self._queue) >= self._batch_size:
                batch = list(self._queue)
                self._queue.clear()
        if batch is not None:
            self._run_flush(batch)

    async def drain(self) -> None:
        """Stop timer and flush remaining events."""
        self.stop()
        with self._lock:
            batch = list(self._queue)
            self._queue.clear()
        if batch:
            await self._flush_fn(batch)

    def _schedule_flush(self) -> None:
        """Schedule the next periodic flush."""
        self._timer = threading.Timer(self._flush_interval, self._on_timer)
        self._timer.daemon = True
        self._timer.start()

    def _on_timer(self) -> None:
        """Timer callback: flush and reschedule."""
        with self._lock:
            batch = list(self._queue)
            self._queue.clear()
        if batch:
            self._run_flush(batch)
        self._schedule_flush()

    def _run_flush(self, batch: list[QueuedEvent]) -> None:
        """Run the async flush function from a sync context (timer thread)."""
        if self._loop is not None and self._loop.is_running():
            asyncio.run_coroutine_threadsafe(self._safe_flush(batch), self._loop)
        else:
            try:
                asyncio.run(self._safe_flush(batch))
            except RuntimeError:
                print(
                    f"AAL flush failed — dropping {len(batch)} events: no event loop",
                    file=sys.stderr,
                )

    async def _safe_flush(self, batch: list[QueuedEvent]) -> None:
        """Flush with error handling."""
        try:
            await self._flush_fn(batch)
        except Exception as exc:
            print(f"AAL flush failed — dropping {len(batch)} events: {exc}", file=sys.stderr)
