"""TTL-bounded inbound dedupe for mesh logical_message_id."""

from __future__ import annotations

import re
import threading
import time
import uuid
from collections import OrderedDict
from mesh.schema_constants import MAX_LOGICAL_MESSAGE_ID_LEN

# UUID canonical (hyphens) or uuid.hex (32 lowercase hex chars). Hyphen placed last in the class.
LOGICAL_ID_PATTERN = re.compile("^[a-fA-F0-9-]{1,%d}$" % MAX_LOGICAL_MESSAGE_ID_LEN)


def normalize_valid_logical_message_id(raw: object) -> str | None:
    """Return normalized id string if valid for dedupe; else None (caller uses legacy handling)."""
    if raw is None:
        return None
    s = str(raw).strip()
    if not s or len(s) > MAX_LOGICAL_MESSAGE_ID_LEN:
        return None
    if LOGICAL_ID_PATTERN.fullmatch(s) is None:
        return None
    return s


def new_logical_message_id() -> str:
    """Generator for outbound payloads (compact, hyphen-free)."""
    return uuid.uuid4().hex


def merged_payload_with_logical_id(payload: dict) -> dict:
    """Copy *payload* and ensure logical_message_id (for JSON size previews and wire sends)."""
    out = dict(payload)
    if not out.get("logical_message_id"):
        out["logical_message_id"] = new_logical_message_id()
    return out


class LogicalMessageDedupeStore:
    """Stores (peer_fingerprint + logical_message_id) with TTL and max-cap eviction."""

    def __init__(self, ttl_seconds: float, max_entries: int, *, now_fn: object | None = None) -> None:
        self._ttl = max(1.0, float(ttl_seconds))
        self._max_entries = max(1, int(max_entries))
        self._now_fn: object = now_fn if now_fn is not None else time.monotonic
        self._lock = threading.Lock()
        self._seen: OrderedDict[str, float] = OrderedDict()

    def has_processed_recently(self, peer_fingerprint: str, logical_id: str) -> bool:
        """True if this (peer, logical id) was already successfully processed within TTL."""
        pf = (peer_fingerprint or "").strip()
        lid = (logical_id or "").strip()
        if not pf or not lid:
            return False
        key = pf + "\x00" + lid
        with self._lock:
            now = float(self._now_fn())  # type: ignore[misc]
            self._prune_expired_unlocked(now)
            return key in self._seen

    def record_processed(self, peer_fingerprint: str, logical_id: str) -> None:
        """Record after a successful inbound handling + outbound mesh reply with this logical id."""
        pf = (peer_fingerprint or "").strip()
        lid = (logical_id or "").strip()
        if not pf or not lid:
            return
        key = pf + "\x00" + lid
        with self._lock:
            now = float(self._now_fn())  # type: ignore[misc]
            self._prune_expired_unlocked(now)
            self._seen[key] = now
            self._seen.move_to_end(key)
            while len(self._seen) > self._max_entries:
                self._seen.popitem(last=False)

    def _prune_expired_unlocked(self, now: float) -> None:
        stale = [k for k, ts in self._seen.items() if now - ts > self._ttl]
        for k in stale:
            self._seen.pop(k, None)
