"""Batch manager for auto-batching data points.

BatchManager handles auto-batching of data points.

Batches are flushed when either:
- The batch size reaches the configured limit (default: 100 points)
- The batch timeout elapses (default: 5.0 seconds)
"""

import asyncio
import logging
from typing import Callable

from .types import IngressDataPoint

logger = logging.getLogger(__name__)


class BatchManager:
    """Auto-batching manager with size and timeout triggers."""

    def __init__(
        self,
        batch_size: int,
        batch_timeout: float,
        on_flush: Callable[[list[IngressDataPoint]], asyncio.Future[None]],
    ) -> None:
        """Initialize batch manager.

        Args:
            batch_size: Number of points before auto-flush
            batch_timeout: Seconds before auto-flush
            on_flush: Async callback to flush batch
        """
        self._points: list[IngressDataPoint] = []
        self._timer_task: asyncio.Task[None] | None = None
        self._batch_size = batch_size
        self._batch_timeout = batch_timeout
        self._on_flush = on_flush

    async def add(self, point: IngressDataPoint) -> None:
        """Add a data point to the current batch.

        Automatically flushes if batch size limit is reached.

        Args:
            point: Data point to add
        """
        self._points.append(point)

        # Start timer if this is the first point
        if len(self._points) == 1:
            self._start_timer()

        # Flush if batch size reached
        if len(self._points) >= self._batch_size:
            await self.flush()

    async def flush(self) -> None:
        """Force flush the current batch immediately.

        Raises:
            Exception: If flush callback fails
        """
        if len(self._points) == 0:
            return

        self._stop_timer()
        batch = self._points.copy()
        self._points = []

        try:
            await self._on_flush(batch)
        except Exception as error:
            # Re-raise to let caller handle error
            raise error

    def _start_timer(self) -> None:
        """Start the batch timeout timer."""
        async def timer_callback() -> None:
            await asyncio.sleep(self._batch_timeout)
            try:
                await self.flush()
            except Exception as error:
                logger.error("Batch flush failed: %s", error)

        self._timer_task = asyncio.create_task(timer_callback())

    def _stop_timer(self) -> None:
        """Stop the batch timeout timer."""
        if self._timer_task and not self._timer_task.done():
            self._timer_task.cancel()
            self._timer_task = None

    def get_pending_count(self) -> int:
        """Get the current number of points in the batch.

        Returns:
            Number of points pending flush
        """
        return len(self._points)
