"""Pairing code service for self-service user onboarding."""

from __future__ import annotations

import logging
import secrets
import string
import time
from typing import TYPE_CHECKING, TypedDict

if TYPE_CHECKING:
    from klir.config import AgentConfig

logger = logging.getLogger(__name__)

_ALPHABET = string.ascii_uppercase + string.digits


class _CodeEntry(TypedDict):
    admin_user_id: int
    created_at: float


class PairingService:
    """Generate, validate, and manage single-use pairing codes."""

    def __init__(self, config: AgentConfig) -> None:
        self._cfg = config.pairing
        self._codes: dict[str, _CodeEntry] = {}

    def generate_code(self, admin_user_id: int) -> str | None:
        """Generate a new pairing code. Returns the code string, or None if limit reached."""
        self._prune_expired()

        if len(self._codes) >= self._cfg.max_active_codes:
            return None

        while True:
            code = "".join(secrets.choice(_ALPHABET) for _ in range(self._cfg.code_length))
            if code not in self._codes:
                break

        self._codes[code] = _CodeEntry(
            admin_user_id=admin_user_id,
            created_at=time.time(),
        )
        logger.info("Pairing code generated by admin=%d", admin_user_id)
        return code

    def validate(self, code: str, user_id: int) -> bool:
        """Validate and consume a pairing code. Returns True if valid."""
        if not self._cfg.enabled:
            return False

        self._prune_expired()
        normalized = code.strip().upper()

        entry = self._codes.pop(normalized, None)
        if entry is None:
            return False

        logger.info("Pairing code consumed by user=%d (admin=%d)", user_id, entry["admin_user_id"])
        return True

    def revoke(self, code: str) -> bool:
        """Revoke a code. Returns True if it existed."""
        return self._codes.pop(code.strip().upper(), None) is not None

    def list_active(self) -> list[dict[str, object]]:
        """Return active (non-expired) codes with metadata."""
        self._prune_expired()
        now = time.time()
        result = []
        for code, entry in self._codes.items():
            remaining = self._cfg.code_ttl_minutes * 60 - (now - entry["created_at"])
            result.append(
                {
                    "code": code,
                    "admin_user_id": entry["admin_user_id"],
                    "remaining_seconds": max(0, int(remaining)),
                }
            )
        return result

    def _prune_expired(self) -> None:
        """Remove expired codes."""
        if not self._codes:
            return
        now = time.time()
        ttl_seconds = self._cfg.code_ttl_minutes * 60
        expired = [k for k, v in self._codes.items() if now - v["created_at"] > ttl_seconds]
        for k in expired:
            del self._codes[k]
