"""GPU remit manager -- controls which backends LLMHosts is allowed to use.

The remit system ensures LLMHosts only uses backends the user has explicitly
granted access to.  This supports per-GPU opt-in/opt-out and time-based schedules.
"""

from __future__ import annotations

import asyncio
import logging
from datetime import datetime
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from llmhosts.config import LLMHostsConfig, RemitConfig

logger = logging.getLogger(__name__)


class RemitWatcher:
    """Watches config for remit changes and applies them in real-time.

    When hot_reload is enabled, polls the config file every *interval* seconds
    and updates the active remit configuration.
    """

    def __init__(self, config: LLMHostsConfig, interval: float = 5.0) -> None:
        self._config = config
        self._interval = interval
        self._task: asyncio.Task[None] | None = None
        self._active_remit: RemitConfig = config.remit

    @property
    def active_remit(self) -> RemitConfig:
        return self._active_remit

    def start(self) -> None:
        """Start the background watcher if hot_reload is enabled."""
        if self._active_remit.hot_reload and self._task is None:
            self._task = asyncio.create_task(self._watch_loop())
            logger.info("Remit watcher started (polling every %.1fs)", self._interval)

    def stop(self) -> None:
        """Stop the background watcher."""
        if self._task is not None:
            self._task.cancel()
            self._task = None
            logger.info("Remit watcher stopped")

    async def _watch_loop(self) -> None:
        """Poll config file for remit changes."""
        try:
            while True:
                await asyncio.sleep(self._interval)
                try:
                    from llmhosts.config import load_config

                    new_config = load_config()
                    if new_config.remit != self._active_remit:
                        logger.info("Remit configuration changed, reloading")
                        self._active_remit = new_config.remit
                except Exception:
                    logger.debug("Failed to reload config for remit update", exc_info=True)
        except asyncio.CancelledError:
            pass

    def is_backend_allowed(self, backend_id: str, gpu_name: str = "") -> bool:
        """Check if a backend is within the current remit.

        Args:
            backend_id: The backend identifier (e.g. URL or name).
            gpu_name: Optional GPU device name for per-GPU filtering.

        Returns:
            True if the backend is allowed under the current remit.
        """
        remit = self._active_remit

        # Check schedule first
        if not self._is_within_schedule(remit):
            return False

        if remit.mode == "all":
            return True
        elif remit.mode == "allowlist":
            return self._matches_list(backend_id, gpu_name, remit.allowlist)
        elif remit.mode == "denylist":
            return not self._matches_list(backend_id, gpu_name, remit.denylist)
        else:
            logger.warning("Unknown remit mode '%s', defaulting to allow-all", remit.mode)
            return True

    def _matches_list(self, backend_id: str, gpu_name: str, entries: list[str]) -> bool:
        """Check if backend_id or gpu_name matches any entry in the list.

        Matching is case-insensitive and checks if the entry is a substring
        of the backend_id or gpu_name (entry-in-value direction only).
        """
        backend_lower = backend_id.lower()
        gpu_lower = gpu_name.lower()
        for entry in entries:
            entry_lower = entry.lower()
            if entry_lower in backend_lower or (gpu_lower and entry_lower in gpu_lower):
                return True
        return False

    @staticmethod
    def _is_within_schedule(remit: RemitConfig) -> bool:
        """Check if current time falls within any schedule entry.

        If no schedule entries exist, always returns True (no time restriction).
        """
        if not remit.schedule:
            return True

        now = datetime.now()
        current_hour = now.hour
        current_day = now.strftime("%a").lower()

        for entry in remit.schedule:
            if current_day in [d.lower() for d in entry.days] and entry.start_hour <= current_hour < entry.end_hour:
                return True

        return False


def filter_backends_by_remit(
    backends: list[str],
    remit_watcher: RemitWatcher,
    gpu_names: dict[str, str] | None = None,
) -> list[str]:
    """Filter a list of backends to only those within the current remit.

    Args:
        backends: List of backend identifiers.
        remit_watcher: Active remit watcher instance.
        gpu_names: Optional mapping of backend_id -> GPU name.

    Returns:
        Filtered list of allowed backends.
    """
    gpu_names = gpu_names or {}
    return [b for b in backends if remit_watcher.is_backend_allowed(b, gpu_names.get(b, ""))]
