"""
Pattern Cache — syncs known failure patterns + fixes from platform.

Periodically pulls patterns from GET /v1/remediation/patterns
and updates the FastDetector's cache for instant <5ms matching.

Background refresh runs every 5 minutes. Initial sync on SDK init.
"""

import asyncio
import hashlib
import logging
import time
from typing import TYPE_CHECKING, Any, Dict, List, Optional

if TYPE_CHECKING:
    from ..judge.selector import JudgeSelector
    from .fast_detector import FastDetector

logger = logging.getLogger("aigie.runtime.pattern_cache")


class PatternCache:
    """Syncs remediation patterns from the Aigie platform.

    Thread-safe: the fast_detector's cache is updated via atomic dict swap.
    """

    def __init__(
        self,
        api_url: str,
        api_key: str | None = None,
        refresh_interval_sec: float = 300.0,  # 5 minutes
        fast_detector: Optional["FastDetector"] = None,
        judge_selector: Optional["JudgeSelector"] = None,
    ):
        self._api_url = api_url.rstrip("/")
        self._api_key = api_key
        self._refresh_interval = refresh_interval_sec
        self._fast_detector = fast_detector
        self._judge_selector = judge_selector
        self._refresh_task: asyncio.Task | None = None
        self._last_sync: float = 0
        self._pattern_count: int = 0
        self._platform_judge_count: int = 0
        self._http_client = None  # Persistent HTTP client (lazily initialized)

    async def initial_sync(self) -> int:
        """Perform initial pattern sync. Returns number of patterns loaded."""
        count = 0
        try:
            patterns, rules, judges = await asyncio.gather(
                self._fetch_patterns(),
                self._fetch_custom_rules(),
                self._fetch_platform_judges(),
                return_exceptions=True,
            )
            if isinstance(patterns, list) and patterns:
                self._apply_patterns(patterns)
                count = len(patterns)
            if isinstance(rules, list) and rules and self._fast_detector:
                self._fast_detector.update_custom_rules(rules)
                logger.debug(f"Loaded {len(rules)} custom detection rules")
            if isinstance(judges, list) and judges and self._judge_selector:
                self._judge_selector.update_platform_judges(judges)
                self._platform_judge_count = len(judges)
                logger.debug(f"Loaded {len(judges)} platform judges")
            self._last_sync = time.monotonic()
            if count:
                logger.info(f"Pattern cache loaded {count} patterns")
        except Exception as e:
            logger.debug(f"Initial pattern sync failed (non-fatal): {e}")
        return count

    def start_background_refresh(self) -> None:
        """Start background refresh task."""
        if self._refresh_task is not None:
            return
        try:
            loop = asyncio.get_running_loop()
            self._refresh_task = loop.create_task(self._refresh_loop())
        except RuntimeError:
            logger.debug("No event loop — pattern refresh disabled")

    async def force_refresh(self) -> int:
        """Force an immediate pattern refresh (e.g. after backend push notification).

        Returns:
            Number of patterns loaded.
        """
        try:
            patterns, rules, judges = await asyncio.gather(
                self._fetch_patterns(),
                self._fetch_custom_rules(),
                self._fetch_platform_judges(),
                return_exceptions=True,
            )
            count = 0
            if isinstance(patterns, list) and patterns:
                self._apply_patterns(patterns)
                count = len(patterns)
            if isinstance(rules, list) and rules and self._fast_detector:
                self._fast_detector.update_custom_rules(rules)
            if isinstance(judges, list) and judges and self._judge_selector:
                self._judge_selector.update_platform_judges(judges)
                self._platform_judge_count = len(judges)
            self._last_sync = time.monotonic()
            if count:
                logger.info(f"Pattern cache force-refreshed: {count} patterns")
            return count
        except Exception as e:
            logger.debug(f"Force refresh failed: {e}")
            return 0

    async def _refresh_loop(self) -> None:
        """Background refresh loop."""
        while True:
            try:
                await asyncio.sleep(self._refresh_interval)
                patterns, rules, judges = await asyncio.gather(
                    self._fetch_patterns(),
                    self._fetch_custom_rules(),
                    self._fetch_platform_judges(),
                    return_exceptions=True,
                )
                if isinstance(patterns, list) and patterns:
                    self._apply_patterns(patterns)
                if isinstance(rules, list) and rules and self._fast_detector:
                    self._fast_detector.update_custom_rules(rules)
                if isinstance(judges, list) and judges and self._judge_selector:
                    self._judge_selector.update_platform_judges(judges)
                    self._platform_judge_count = len(judges)
                self._last_sync = time.monotonic()
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.debug(f"Pattern refresh failed: {e}")

    async def _get_http_client(self):
        """Get or create persistent HTTP client."""
        if self._http_client is None:
            import httpx

            headers = {"Content-Type": "application/json"}
            if self._api_key:
                headers["X-API-Key"] = self._api_key
            self._http_client = httpx.AsyncClient(
                timeout=10.0,
                headers=headers,
            )
        return self._http_client

    async def _fetch_patterns(self) -> List[Dict[str, Any]]:
        """Fetch patterns from platform API."""
        client = await self._get_http_client()
        url = f"{self._api_url}/v1/remediation/patterns"
        resp = await client.get(url)
        if resp.status_code == 200:
            data = resp.json()
            return data if isinstance(data, list) else data.get("patterns", [])
        return []

    async def _fetch_custom_rules(self) -> List[Dict[str, Any]]:
        """Fetch custom quality rules from platform API."""
        try:
            client = await self._get_http_client()
            url = f"{self._api_url}/v1/detection/rules"
            resp = await client.get(url)
            if resp.status_code == 200:
                data = resp.json()
                return data if isinstance(data, list) else data.get("rules", [])
        except Exception as e:
            logger.debug(f"Custom rules fetch failed (non-fatal): {e}")
        return []

    async def _fetch_platform_judges(self) -> List[Dict[str, Any]]:
        """Fetch predefined judges from Kytte platform.

        Returns list of judge dicts with: judge_id, name, category,
        template_id, signal_triggers, span_types, skills, priority.
        """
        try:
            client = await self._get_http_client()
            url = f"{self._api_url}/v1/evals/judges"
            resp = await client.get(url)
            if resp.status_code == 200:
                data = resp.json()
                return data if isinstance(data, list) else data.get("judges", [])
        except Exception as e:
            logger.debug(f"Platform judges fetch failed (non-fatal): {e}")
        return []

    def _apply_patterns(self, patterns: List[Dict[str, Any]]) -> None:
        """Convert platform patterns to fast-lookup cache and apply."""
        cache: Dict[str, Dict[str, Any]] = {}
        for p in patterns:
            # Index by error type
            error_type = p.get("error_type", "")
            if error_type:
                cache[error_type] = p

            # Also index by error signature hash for fuzzy matching
            heuristic = p.get("judge_heuristic", "")
            if heuristic:
                heuristic_str = str(heuristic) if not isinstance(heuristic, str) else heuristic
                sig = hashlib.md5(
                    heuristic_str[:200].lower().encode(), usedforsecurity=False
                ).hexdigest()[:16]
                cache[sig] = p

            # Index by pattern name
            name = p.get("name", "")
            if name:
                cache[name] = p

        self._pattern_count = len(patterns)
        if self._fast_detector:
            self._fast_detector.update_pattern_cache(cache)

    async def stop(self) -> None:
        """Stop background refresh."""
        if self._refresh_task:
            self._refresh_task.cancel()
            try:
                await self._refresh_task
            except asyncio.CancelledError:
                pass
            self._refresh_task = None

        if self._http_client:
            await self._http_client.aclose()
            self._http_client = None

    def get_stats(self) -> Dict[str, Any]:
        return {
            "pattern_count": self._pattern_count,
            "platform_judge_count": self._platform_judge_count,
            "last_sync_ago_sec": time.monotonic() - self._last_sync if self._last_sync else None,
            "refresh_interval_sec": self._refresh_interval,
        }
