"""Async database writer for monitoring.

Buffers monitoring records in memory and flushes to PostgreSQL
in batches to minimize impact on request latency.
"""

from __future__ import annotations

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

import loguru

logger = loguru.logger.bind(component="monitoring.writer")


class AsyncDBWriter:
    """Batched asynchronous database writer.

    Features:
    - Configurable batch size and flush interval
    - Background asyncio task for periodic flushing
    - Graceful shutdown with final flush
    - Error resilience — failed writes are logged but don't crash
    """

    def __init__(
        self,
        batch_size: int = 100,
        flush_interval_ms: int = 1000,
    ) -> None:
        self.batch_size = batch_size
        self.flush_interval_ms = flush_interval_ms
        self._buffer: list[
            tuple[
                Callable[..., Coroutine[Any, Any, None]],
                tuple[Any, ...],
                dict[str, Any],
            ]
        ] = []
        self._lock = asyncio.Lock()
        self._flush_task: asyncio.Task[None] | None = None
        self._running = False

    async def write(
        self,
        coro_fn: Callable[..., Coroutine[Any, Any, None]],
        *args: Any,
        **kwargs: Any,
    ) -> None:
        """Buffer a database operation for async writing.

        Args:
            coro_fn: Async function to call (e.g., repository.insert_trace).
            *args: Positional arguments to pass to the function.
            **kwargs: Keyword arguments to pass to the function.
        """
        async with self._lock:
            self._buffer.append((coro_fn, args, kwargs))

            if len(self._buffer) >= self.batch_size:
                await self._flush_buffer()

    async def flush(self) -> None:
        """Flush all buffered records to database."""
        async with self._lock:
            await self._flush_buffer()

    async def _flush_buffer(self) -> None:
        """Internal flush — caller must hold _lock."""
        if not self._buffer:
            return

        batch = self._buffer.copy()
        self._buffer.clear()

        for coro_fn, args, kwargs in batch:
            try:
                await coro_fn(*args, **kwargs)
            except Exception as e:
                logger.warning(f"Monitoring write failed: {e}")

    async def start(self) -> None:
        """Start the background flush loop."""
        if self._running:
            return
        self._running = True
        self._flush_task = asyncio.create_task(self._flush_loop())

    async def stop(self) -> None:
        """Stop the writer and flush remaining records."""
        self._running = False
        if self._flush_task:
            self._flush_task.cancel()
            try:
                await self._flush_task
            except asyncio.CancelledError:
                pass
            self._flush_task = None

        # Final flush
        await self.flush()

    async def _flush_loop(self) -> None:
        """Background loop that flushes at regular intervals."""
        interval = self.flush_interval_ms / 1000.0
        while self._running:
            try:
                await asyncio.sleep(interval)
                await self.flush()
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.warning(f"Monitoring flush loop error: {e}")
