"""Provider router — intelligent selection of LLM provider by task, cost, latency.

Includes optional TTL-based response caching via kernel.cache to avoid
redundant LLM calls for identical prompt/model combinations.
"""

from __future__ import annotations

import time

from orxaq.kernel.cache import _SENTINEL, TTLCache, get_registry
from orxaq.orchestration.providers.base import LLMProvider, LLMResponse
from orxaq.orchestration.providers.registry import ProviderRegistry

# Default priority: local first, then cloud by cost.
DEFAULT_PRIORITY = ["vllm", "anthropic", "openai"]

# Task-type to preferred provider mapping.
TASK_ROUTES: dict[str, list[str]] = {
    "fast": ["vllm"],
    "reasoning": ["anthropic", "openai"],
    "coding": ["anthropic", "openai"],
    "creative": ["openai", "anthropic"],
    "general": DEFAULT_PRIORITY,
}


class ProviderRouter:
    """Route generation requests to the best available provider.

    Selection strategy:
    1. Check response cache for identical requests.
    2. Filter by task type preference (if specified).
    3. Respect explicit priority ordering.
    4. Fallback through chain until one succeeds.
    5. Cache successful responses for future dedup.
    """

    def __init__(
        self,
        registry: ProviderRegistry,
        priority: list[str] | None = None,
        cache_maxsize: int = 256,
        cache_ttl: float = 300.0,
        enable_cache: bool = True,
    ) -> None:
        self.registry = registry
        self.priority = priority or DEFAULT_PRIORITY
        self._latency_history: dict[str, list[float]] = {}
        self._failure_counts: dict[str, int] = {}
        self._cache_enabled = enable_cache
        self._cache = TTLCache(maxsize=cache_maxsize, ttl_seconds=cache_ttl)
        if enable_cache:
            get_registry().register("provider_router", self._cache)

    def _candidates(self, task_type: str | None = None) -> list[LLMProvider]:
        """Get ordered candidate providers for a task type."""
        if task_type and task_type in TASK_ROUTES:
            order = TASK_ROUTES[task_type]
        else:
            order = self.priority

        candidates = []
        for name in order:
            provider = self.registry.get(name)
            if provider is not None:
                candidates.append(provider)

        # Append any registered providers not in the priority list.
        seen = {p.name for p in candidates}
        for provider in self.registry.all_providers():
            if provider.name not in seen:
                candidates.append(provider)

        return candidates

    @staticmethod
    def _cache_key(
        prompt: str,
        task_type: str | None,
        model: str | None,
        max_tokens: int,
        temperature: float,
    ) -> str:
        """Build a deterministic cache key for a route request."""
        return f"{hash((prompt, task_type, model, max_tokens, temperature))}"

    def route(
        self,
        prompt: str,
        task_type: str | None = None,
        model: str | None = None,
        max_tokens: int = 1024,
        temperature: float = 0.7,
    ) -> LLMResponse:
        """Route a prompt to the best available provider.

        Tries providers in priority order, falling back on failure.
        Caches successful responses when caching is enabled.
        """
        # Check cache first.
        key = ""
        if self._cache_enabled:
            key = self._cache_key(prompt, task_type, model, max_tokens, temperature)
            cached = self._cache.get(key)
            if cached is not _SENTINEL:
                return cached  # type: ignore[no-any-return]  # cache stores LLMResponse

        candidates = self._candidates(task_type)
        if not candidates:
            return LLMResponse(
                text="",
                model=model or "none",
                provider="router",
                metadata={"error": "no providers registered"},
            )

        errors: list[str] = []
        for provider in candidates:
            start = time.monotonic()
            response = provider.generate(
                prompt,
                model=model,
                max_tokens=max_tokens,
                temperature=temperature,
            )
            elapsed = (time.monotonic() - start) * 1000

            # Track latency.
            if provider.name not in self._latency_history:
                self._latency_history[provider.name] = []
            self._latency_history[provider.name].append(elapsed)

            # Check for errors in the response.
            error = response.metadata.get("error")
            if error:
                self._failure_counts[provider.name] = self._failure_counts.get(provider.name, 0) + 1
                errors.append(f"{provider.name}: {error}")
                continue

            # Success — reset failure count and cache.
            self._failure_counts[provider.name] = 0
            if self._cache_enabled:
                self._cache.put(key, response)
            return response

        # All providers failed.
        return LLMResponse(
            text="",
            model=model or "none",
            provider="router",
            metadata={
                "error": "all providers failed",
                "details": errors,
            },
        )

    def health_check(self) -> dict[str, dict]:
        """Health-check all providers via registry."""
        return self.registry.health_check_all()

    def stats(self) -> dict:
        """Return routing statistics including cache performance."""
        latency_stats: dict[str, dict] = {}
        for name, latencies in self._latency_history.items():
            if latencies:
                latency_stats[name] = {
                    "calls": len(latencies),
                    "avg_ms": sum(latencies) / len(latencies),
                    "min_ms": min(latencies),
                    "max_ms": max(latencies),
                }
        total_lookups = self._cache.hits + self._cache.misses
        return {
            "latency": latency_stats,
            "failures": dict(self._failure_counts),
            "priority": self.priority,
            "cache": {
                "enabled": self._cache_enabled,
                "entries": len(self._cache),
                "hits": self._cache.hits,
                "misses": self._cache.misses,
                "evictions": self._cache.evictions,
                "hit_rate": (self._cache.hits / total_lookups if total_lookups else 0.0),
            },
        }

    def clear_cache(self) -> None:
        """Clear the response cache."""
        self._cache.clear()
