"""Batch queue implementations for sync and async contexts."""

from __future__ import annotations

import asyncio
import logging
import threading
from typing import Any, Callable, Coroutine, Dict, List

from .types import FlushResult

logger = logging.getLogger("kanban_analytics")


# ---------------------------------------------------------------------------
# Sync BatchQueue  (thread-safe, uses threading.Timer)
# ---------------------------------------------------------------------------


class BatchQueue:
    """Thread-safe batch queue for synchronous contexts.

    Accumulates events and flushes them in batches, either when the
    batch-size threshold is reached or on a recurring timer.
    """

    def __init__(
        self,
        transport: Callable[[List[Dict[str, Any]]], None],
        batch_size: int = 50,
        flush_interval: float = 2.0,
        debug: bool = False,
    ) -> None:
        self._transport = transport
        self._batch_size = batch_size
        self._flush_interval = flush_interval
        self._debug = debug

        self._queue: List[Dict[str, Any]] = []
        self._lock = threading.Lock()
        self._timer: threading.Timer | None = None
        self._destroyed = False

        self._start_timer()

    # -- public API ---------------------------------------------------------

    def enqueue(self, event: Dict[str, Any]) -> None:
        """Add an event to the queue.  Triggers flush at *batch_size*."""
        if self._destroyed:
            return

        with self._lock:
            self._queue.append(event)
            self._debug_log(f"enqueued: {event.get('type')} ({event.get('messageId')})")
            should_flush = len(self._queue) >= self._batch_size

        if should_flush:
            self.flush()

    def flush(self) -> FlushResult:
        """Send all queued events as a single batch."""
        with self._lock:
            if not self._queue:
                return {"sent": 0, "failed": 0}
            batch = self._queue
            self._queue = []

        try:
            self._transport(batch)
            self._debug_log(f"flushed {len(batch)} event(s)")
            return {"sent": len(batch), "failed": 0}
        except Exception:
            if self._debug:
                logger.exception("[Analytics SDK] Batch flush failed")
            return {"sent": 0, "failed": len(batch)}

    def destroy(self) -> FlushResult:
        """Flush remaining events, then stop the timer."""
        self._destroyed = True
        self._stop_timer()
        result = self.flush()
        self._debug_log("batch queue destroyed")
        return result

    @property
    def length(self) -> int:
        with self._lock:
            return len(self._queue)

    # -- private helpers ----------------------------------------------------

    def _start_timer(self) -> None:
        def _tick() -> None:
            if not self._destroyed:
                self.flush()
                self._start_timer()

        self._timer = threading.Timer(self._flush_interval, _tick)
        self._timer.daemon = True
        self._timer.start()

    def _stop_timer(self) -> None:
        if self._timer is not None:
            self._timer.cancel()
            self._timer = None

    def _debug_log(self, message: str) -> None:
        if self._debug:
            logger.debug("[Analytics SDK] %s", message)


# ---------------------------------------------------------------------------
# Async BatchQueue  (uses asyncio.Lock / asyncio.Task)
# ---------------------------------------------------------------------------


class AsyncBatchQueue:
    """Async batch queue for async contexts.

    Uses :class:`asyncio.Lock` for concurrency safety and
    :func:`asyncio.create_task` for the periodic flush timer.
    """

    def __init__(
        self,
        transport: Callable[[List[Dict[str, Any]]], Coroutine[Any, Any, None]],
        batch_size: int = 50,
        flush_interval: float = 2.0,
        debug: bool = False,
    ) -> None:
        self._transport = transport
        self._batch_size = batch_size
        self._flush_interval = flush_interval
        self._debug = debug

        self._queue: List[Dict[str, Any]] = []
        self._lock = asyncio.Lock()
        self._flush_task: asyncio.Task[None] | None = None
        self._destroyed = False

    async def start(self) -> None:
        """Start the periodic flush timer.  Call after the event loop is running."""
        self._flush_task = asyncio.create_task(self._flush_loop())

    # -- public API ---------------------------------------------------------

    async def enqueue(self, event: Dict[str, Any]) -> None:
        if self._destroyed:
            return

        should_flush = False
        async with self._lock:
            self._queue.append(event)
            self._debug_log(f"enqueued: {event.get('type')} ({event.get('messageId')})")
            should_flush = len(self._queue) >= self._batch_size

        if should_flush:
            await self.flush()

    async def flush(self) -> FlushResult:
        async with self._lock:
            if not self._queue:
                return {"sent": 0, "failed": 0}
            batch = self._queue
            self._queue = []

        try:
            await self._transport(batch)
            self._debug_log(f"flushed {len(batch)} event(s)")
            return {"sent": len(batch), "failed": 0}
        except Exception:
            if self._debug:
                logger.exception("[Analytics SDK] Async batch flush failed")
            return {"sent": 0, "failed": len(batch)}

    async def destroy(self) -> FlushResult:
        self._destroyed = True
        if self._flush_task is not None:
            self._flush_task.cancel()
            try:
                await self._flush_task
            except asyncio.CancelledError:
                pass
            self._flush_task = None
        result = await self.flush()
        self._debug_log("async batch queue destroyed")
        return result

    @property
    def length(self) -> int:
        return len(self._queue)

    # -- private helpers ----------------------------------------------------

    async def _flush_loop(self) -> None:
        """Periodically flush the queue."""
        try:
            while not self._destroyed:
                await asyncio.sleep(self._flush_interval)
                if not self._destroyed:
                    await self.flush()
        except asyncio.CancelledError:
            pass

    def _debug_log(self, message: str) -> None:
        if self._debug:
            logger.debug("[Analytics SDK] %s", message)
