from __future__ import annotations

import asyncio
from threading import Lock
from typing import Any


class V1ReplyTracker:
    """Tracks pending request futures keyed by outbound msg_id."""

    def __init__(self) -> None:
        self._pending: dict[str, asyncio.Future[Any]] = {}
        self._lock = Lock()

    def register(self, msg_id: str) -> asyncio.Future[Any]:
        normalized = _normalize_non_empty(msg_id, field_name="msg_id")
        loop = asyncio.get_running_loop()
        future: asyncio.Future[Any] = loop.create_future()
        with self._lock:
            if normalized in self._pending:
                raise ValueError(f"reply_tracker_duplicate_msg_id:{normalized}")
            self._pending[normalized] = future
        return future

    def complete(self, *, reply_to_msg_id: str, value: Any) -> bool:
        future = self._pop(reply_to_msg_id)
        if future is None:
            return False
        if not future.done():
            future.set_result(value)
        return True

    def fail(self, *, reply_to_msg_id: str, exc: BaseException) -> bool:
        future = self._pop(reply_to_msg_id)
        if future is None:
            return False
        if not future.done():
            future.set_exception(exc)
        return True

    def cancel(self, msg_id: str) -> bool:
        future = self._pop(msg_id)
        if future is None:
            return False
        return future.cancel()

    def cancel_all(self) -> int:
        with self._lock:
            futures = list(self._pending.values())
            self._pending.clear()
        cancelled = 0
        for future in futures:
            if future.cancel():
                cancelled += 1
        return cancelled

    def pending_count(self) -> int:
        with self._lock:
            return len(self._pending)

    def _pop(self, msg_id: str) -> asyncio.Future[Any] | None:
        normalized = _normalize_non_empty(msg_id, field_name="msg_id")
        with self._lock:
            return self._pending.pop(normalized, None)


def _normalize_non_empty(value: Any, *, field_name: str) -> str:
    normalized = str(value or "").strip()
    if not normalized:
        raise ValueError(f"{field_name} must be non-empty.")
    return normalized


__all__ = ["V1ReplyTracker"]
