"""Internal logging engine for Phase 2 logger core."""

from __future__ import annotations

import threading

from vedatrace.batching.batcher import Batcher
from vedatrace.config import VedaTraceConfig
from vedatrace.models import LogRecord
from vedatrace.safe import safe_invoke
from vedatrace.transports.base import Transport


class Engine:
    """Internal sink that accepts log records.

    Phase 2 note:
    This engine only stores records in memory for core-logger tests.
    """

    def __init__(
        self,
        config: VedaTraceConfig,
        *,
        transports: list[Transport] | None = None,
    ) -> None:
        self._config = config
        self._records: list[LogRecord] = []
        self._fail_emit = False
        if transports is not None:
            self._transports = list(transports)
        elif config.transports is not None:
            self._transports = list(config.transports)
        else:
            self._transports = []
        self._state_lock = threading.Lock()
        self._closed = False
        self._flush_interval_seconds = 0.0
        self._flush_timer: threading.Timer | None = None
        self._batcher: Batcher | None = None
        if config.batching.enabled:
            self._batcher = Batcher(
                batch_size=config.batching.batch_size,
                on_flush=self._flush_records,
            )
            self._flush_interval_seconds = config.batching.flush_interval_seconds
            if self._flush_interval_seconds > 0:
                self._schedule_flush_timer()

    def emit(self, record: LogRecord) -> None:
        """Accept a record and store it in memory."""

        with self._state_lock:
            if self._closed:
                return
        if self._fail_emit:
            self._handle_error(RuntimeError("engine emit failure (test)"))
            return
        self._records.append(record)
        if self._batcher is None:
            self._flush_records([record])
            return
        should_flush = self._batcher.add(record)
        if should_flush:
            self._batcher.flush()

    def flush(self) -> None:
        """Flush pending records.

        If batching is enabled, flush remaining buffered records.
        """

        if self._batcher is not None:
            self._batcher.flush()
        return None

    def close(self) -> None:
        """Close engine resources.

        Flush batched records then close transports.
        """

        timer_to_cancel: threading.Timer | None
        with self._state_lock:
            if self._closed:
                return None
            self._closed = True
            timer_to_cancel = self._flush_timer
            self._flush_timer = None
        if timer_to_cancel is not None:
            timer_to_cancel.cancel()

        self.flush()
        if self._batcher is not None:
            self._batcher.close()
        for transport in self._transports:
            try:
                transport.close()
            except BaseException as exc:
                self._handle_error(exc)
        return None

    @property
    def _test_records(self) -> tuple[LogRecord, ...]:
        """WARNING: test-only view of in-memory records."""

        return tuple(self._records)

    def _set_fail_emit_for_tests(self, should_fail: bool) -> None:
        """WARNING: test-only helper to force emit failures."""

        self._fail_emit = should_fail

    def _handle_error(self, exc: BaseException) -> None:
        """Route internal engine failures to configured error callback."""

        safe_invoke(self._config.on_error, exc)

    def _flush_records(self, records: list[LogRecord]) -> None:
        """Send flushed records to each configured transport safely."""

        for transport in self._transports:
            try:
                transport.emit(records)
            except BaseException as exc:
                self._handle_error(exc)

    def _schedule_flush_timer(self) -> None:
        """Schedule next periodic flush tick when enabled."""

        with self._state_lock:
            if self._closed:
                return
            if self._batcher is None:
                return
            if self._flush_interval_seconds <= 0:
                return
            timer = threading.Timer(self._flush_interval_seconds, self._on_flush_timer_tick)
            timer.daemon = True
            self._flush_timer = timer
        timer.start()

    def _on_flush_timer_tick(self) -> None:
        """Timer callback that flushes and reschedules safely."""

        try:
            self.flush()
        except BaseException as exc:
            self._handle_error(exc)
        finally:
            with self._state_lock:
                self._flush_timer = None
                should_reschedule = (
                    not self._closed
                    and self._batcher is not None
                    and self._flush_interval_seconds > 0
                )
            if should_reschedule:
                self._schedule_flush_timer()
