"""Drop-oldest + ``kind="overrun"`` emission policy (AZ-274 / E-CC-FDR-CLIENT).

Plugs into :attr:`FdrClient.on_overrun` (the contract's documented hook).
The closure:

1. Pops the oldest record from the buffer to make room (drop-oldest).
2. Retries the user's record once.
3. Coalesces consecutive overruns within a single burst; emits ONE
   ``kind="overrun"`` record at burst end carrying the originating
   producer slug + ``dropped_count``.
4. ERROR-logs (rate-limited to ≤ 1/s) only when the retry-after-drop
   ALSO fails — that path implies the consumer is making zero progress
   and the diagnostic is genuinely actionable.

Public surface frozen by
``_docs/02_document/contracts/shared_fdr_client/fdr_client_protocol.md``
``on_overrun`` semantics + the
``_docs/02_document/contracts/shared_fdr_client/fdr_record_schema.md``
overrun payload shape.
"""

from __future__ import annotations

import datetime as _dt
import time
from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING, Final

from gps_denied_onboard.fdr_client.records import (
    CURRENT_SCHEMA_VERSION,
    OVERRUN_KIND,
    OVERRUN_PRODUCER_ID,
    FdrRecord,
)
from gps_denied_onboard.logging import get_logger

if TYPE_CHECKING:
    from gps_denied_onboard.fdr_client.client import FdrClient

__all__ = ["default_overrun_policy"]


_DIAG_LOGGER_NAME: Final[str] = "shared.fdr_client.overrun"

# Minimum spacing between diagnostic ERRORs about retry-after-drop failures.
# AZ-274 AC-6: "≤ 1 ERROR/sec per FdrClient on sustained overruns".
_ERROR_LOG_MIN_INTERVAL_S: Final[float] = 1.0


def _iso_utc_now() -> str:
    """RFC 3339 UTC timestamp with microsecond precision and ``Z`` suffix."""
    now = _dt.datetime.now(_dt.timezone.utc)
    return now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{now.microsecond:06d}Z"


@dataclass
class _BurstState:
    """Coalescing state for a single in-flight overrun burst."""

    dropped_count: int = 0
    originating_producer_id: str = ""

    def reset(self) -> None:
        self.dropped_count = 0
        self.originating_producer_id = ""


def default_overrun_policy(client: FdrClient) -> Callable[[FdrRecord], None]:
    """Return the canonical drop-oldest closure for ``client``.

    Wire into ``client.on_overrun``. Called by :meth:`FdrClient.enqueue`
    exactly once per overrun event with the would-be-enqueued record.
    """
    burst = _BurstState()
    last_error_log_t: list[float] = [0.0]  # boxed to be writable in the closure

    def _evict_one(also_count_as_user_loss: bool) -> bool:
        """Drop one record from the buffer's head.

        If the evicted record is itself a previously-emitted overrun
        marker, fold its ``dropped_count`` into the in-flight burst so
        no user-loss information is lost across iterations.

        ``also_count_as_user_loss=True`` adds 1 to ``burst.dropped_count``
        if the evicted record is a user record. Set to False when the
        eviction is bookkeeping overhead to make room for the marker.

        Returns False iff the buffer was empty.
        """
        dropped = client.drop_oldest_for_overrun()
        if dropped is None:
            return False
        burst.originating_producer_id = client.producer_id
        if isinstance(dropped, FdrRecord) and dropped.kind == OVERRUN_KIND:
            inner_dc = dropped.payload.get("dropped_count")
            if isinstance(inner_dc, int) and inner_dc > 0:
                burst.dropped_count += inner_dc
        elif also_count_as_user_loss:
            burst.dropped_count += 1
        return True

    def policy(offending_record: FdrRecord) -> None:
        # Make room for the user record. ``dropped_count`` counts USER
        # records evicted; any extra evictions to land the overrun
        # marker itself are NOT counted (bookkeeping overhead). When
        # an evicted record is a prior marker, its count is folded
        # into ``burst.dropped_count`` to preserve information.
        evicted_for_user = _evict_one(also_count_as_user_loss=True)

        # One retry only. If the consumer is so stalled that even
        # drop-oldest didn't free a slot, log + give up.
        retry_ok = client._buffer.push(offending_record)
        if not retry_ok:
            _log_retry_failure(client.producer_id, last_error_log_t)
            burst.reset()
            return

        if not evicted_for_user or burst.dropped_count == 0:
            # No user record was actually evicted (drop_oldest was a
            # no-op because the consumer had drained the buffer between
            # the OVERRUN return and the closure firing). Nothing to
            # record — the retry already landed the user's record.
            burst.reset()
            return

        # Emit the overrun marker immediately so the consumer sees it
        # in causal order with the user record that just landed.
        overrun_record = _build_overrun_record(
            originating_producer_id=burst.originating_producer_id,
            dropped_count=burst.dropped_count,
        )
        slipped = client._buffer.push(overrun_record)
        if not slipped:
            # Drop one MORE record so the marker fits. Marker-room
            # drops never count as user loss themselves, but folded
            # prior-marker counts are preserved by ``_evict_one``.
            if not _evict_one(also_count_as_user_loss=False):
                _log_retry_failure(client.producer_id, last_error_log_t)
                burst.reset()
                return
            # Re-build because ``burst.dropped_count`` may have grown
            # after folding a prior marker's count.
            overrun_record = _build_overrun_record(
                originating_producer_id=burst.originating_producer_id,
                dropped_count=burst.dropped_count,
            )
            slipped = client._buffer.push(overrun_record)
            if not slipped:
                _log_retry_failure(client.producer_id, last_error_log_t)
                burst.reset()
                return
        burst.reset()

    return policy


def _build_overrun_record(originating_producer_id: str, dropped_count: int) -> FdrRecord:
    """Synthesise the canonical ``kind="overrun"`` record."""
    return FdrRecord(
        schema_version=CURRENT_SCHEMA_VERSION,
        ts=_iso_utc_now(),
        producer_id=OVERRUN_PRODUCER_ID,
        kind=OVERRUN_KIND,
        payload={
            "producer_id": originating_producer_id,
            "dropped_count": dropped_count,
        },
    )


def _log_retry_failure(producer_id: str, last_t: list[float]) -> None:
    """Emit at most one ERROR per ``_ERROR_LOG_MIN_INTERVAL_S`` per client."""
    now = time.monotonic()
    if now - last_t[0] < _ERROR_LOG_MIN_INTERVAL_S:
        return
    last_t[0] = now
    get_logger(_DIAG_LOGGER_NAME).error(
        "FDR overrun retry-after-drop failed",
        extra={
            "component": _DIAG_LOGGER_NAME,
            "kind": "fdr.overrun_retry_failed",
            "kv": {"producer_id": producer_id},
        },
    )
