"""Resolve alert/rule context to a real Playbook ID.

The learning loop updates EWMA trust only when ``approval_records`` carries the
actual ``playbooks.playbook_id``. YAML rule IDs such as ``host_resource_alert``
are not Playbook IDs, so this resolver bridges rule/alert context to the
canonical DB identity before an ApprovalRecord is created.
"""

from __future__ import annotations

from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path

import structlog
import yaml
from sqlalchemy import text as sa_text

from src.db.base import get_db_context

logger = structlog.get_logger(__name__)


@dataclass(frozen=True)
class PlaybookMatch:
    playbook_id: str
    source: str


async def resolve_playbook_id_for_alert(
    *,
    rule_id: str | None = None,
    alertname: str | None = None,
    affected_services: list[str] | None = None,
    severity: str | None = None,
) -> str | None:
    """Return a real ``playbooks.playbook_id`` for alert context if available."""

    match = await _resolve_exact_yaml_rule(rule_id=rule_id, alertname=alertname)
    if match:
        return match.playbook_id

    match = await _resolve_by_recommendation(
        alertname=alertname,
        affected_services=affected_services or [],
        severity=severity,
    )
    return match.playbook_id if match else None


async def _resolve_exact_yaml_rule(
    *,
    rule_id: str | None,
    alertname: str | None,
) -> PlaybookMatch | None:
    """Use deterministic DB fields before falling back to fuzzy recommendations."""

    rule_id = (rule_id or "").strip()
    alertname = (alertname or "").strip()
    if not rule_id and not alertname:
        return None

    alertname_candidates = [alertname]
    alertname_candidates.extend(_alertnames_for_rule_id(rule_id))
    alertname_candidates = list(dict.fromkeys(name for name in alertname_candidates if name))

    try:
        async with get_db_context() as db:
            if rule_id:
                row = (
                    await db.execute(
                        sa_text(
                            """
                            SELECT playbook_id
                            FROM playbooks
                            WHERE source = 'yaml_rule'
                              AND status = 'approved'
                              AND (
                                name = ('AutoMigrated: ' || :rule_id)
                                OR notes ILIKE ('%rule.id=' || :rule_id || '%')
                              )
                            ORDER BY updated_at DESC
                            LIMIT 1
                            """
                        ),
                        {"rule_id": rule_id},
                    )
                ).first()
                if row:
                    return PlaybookMatch(playbook_id=str(row[0]), source="exact_yaml_rule")

            for candidate in alertname_candidates:
                row = (
                    await db.execute(
                        sa_text(
                            """
                            SELECT playbook_id
                            FROM playbooks
                            WHERE status = 'approved'
                              AND (symptom_pattern::jsonb->'alert_names') ? :alertname
                            ORDER BY
                              CASE WHEN source = 'yaml_rule' THEN 0 ELSE 1 END,
                              updated_at DESC
                            LIMIT 1
                            """
                        ),
                        {"alertname": candidate},
                    )
                ).first()
                if row:
                    return PlaybookMatch(playbook_id=str(row[0]), source="exact_alertname")

            return None
    except Exception as exc:
        logger.warning(
            "playbook_exact_match_failed",
            rule_id=rule_id,
            alertname=alertname,
            error=str(exc),
        )
    return None


@lru_cache(maxsize=1)
def _rule_alertname_index() -> dict[str, tuple[str, ...]]:
    rules_path = Path(__file__).resolve().parents[2] / "alert_rules.yaml"
    try:
        data = yaml.safe_load(rules_path.read_text(encoding="utf-8")) or {}
    except Exception as exc:
        logger.debug("playbook_rule_index_load_failed", path=str(rules_path), error=str(exc))
        return {}

    index: dict[str, tuple[str, ...]] = {}
    for rule in data.get("rules", []):
        if not isinstance(rule, dict):
            continue
        rule_id = str(rule.get("id") or "").strip()
        alertnames = rule.get("match", {}).get("alertname", [])
        if rule_id and isinstance(alertnames, list):
            index[rule_id] = tuple(str(name) for name in alertnames if name)
    return index


def _alertnames_for_rule_id(rule_id: str) -> tuple[str, ...]:
    if not rule_id:
        return ()
    return _rule_alertname_index().get(rule_id, ())


async def _resolve_by_recommendation(
    *,
    alertname: str | None,
    affected_services: list[str],
    severity: str | None,
) -> PlaybookMatch | None:
    alertname = (alertname or "").strip()
    if not alertname and not affected_services:
        return None

    try:
        from src.models.playbook import SymptomPattern
        from src.services.playbook_service import get_playbook_service

        symptoms = SymptomPattern(
            alert_names=[alertname] if alertname else [],
            affected_services=affected_services,
            severity_range=[severity or "P2"],
        )
        recommendations = await get_playbook_service().get_recommendations(
            symptoms=symptoms,
            top_k=1,
            use_rag=False,
        )
        if not recommendations:
            return None
        best = recommendations[0]
        if best.similarity_score < 0.5:
            return None
        return PlaybookMatch(
            playbook_id=best.playbook.playbook_id,
            source="symptom_recommendation",
        )
    except Exception as exc:
        logger.debug(
            "playbook_recommendation_match_skipped",
            alertname=alertname,
            affected_services=affected_services,
            error=str(exc),
        )
        return None
