"""Three-tier FixGenerator: Trust Replay -> Relationship RAG -> Cortex AI."""

from __future__ import annotations

import logging
from typing import Any, Dict, List, Optional

from .fix_policies import human_approval_required, should_use_trust_replay

logger = logging.getLogger(__name__)

# Risk tiers by fix source
RISK_TIERS = {
    "trust_replay": "low",
    "relationship_rag": "medium",
    "cortex_ai": "high",
    "no_fix": "high",
}


def generate_fix(
    event: Dict[str, Any],
    trace_result: Dict[str, Any],
    index_path: str = "data/codex_graphrag/index.json",
    samples: Optional[Dict] = None,
    column_metadata: Optional[Dict] = None,
    cortex_client: Optional[Any] = None,
    erp_type: str = "UNKNOWN",
    allow_cortex: bool = False,
) -> Dict[str, Any]:
    """Three-tier fix generation.

    Returns:
        {
            fix_type: "trust_replay" | "relationship_rag" | "cortex_ai" | "no_fix",
            fix_detail: str,
            risk_tier: "low" | "medium" | "high",
            similar_cases: List[Dict],
            confidence: float,
            patch: Optional[Dict],
            validation_suite: Optional[Dict],
            tier_path: List[str],
            decision_reasons: List[str],
            requires_human_approval: bool,
        }
    """
    tier_path: List[str] = []
    decision_reasons: List[str] = []

    # Tier 1: Trust case replay
    result = _try_trust_replay(trace_result)
    tier_path.append("trust_replay")
    if result["fix_type"] == "trust_replay":
        return _finalize_result(result, tier_path=tier_path)
    decision_reasons.extend(result.get("decision_reasons", []))
    decision_reasons.append("trust_replay_not_applicable")

    # Tier 2: Relationship discovery RAG
    result = _try_relationship_rag(
        event=event,
        trace_result=trace_result,
        samples=samples,
        column_metadata=column_metadata,
        erp_type=erp_type,
    )
    tier_path.append("relationship_rag")
    if result["fix_type"] == "relationship_rag":
        return _finalize_result(result, tier_path=tier_path, extra_reasons=decision_reasons)
    decision_reasons.extend(result.get("decision_reasons", []))
    decision_reasons.append("relationship_rag_not_applicable")

    # Tier 3 can be explicitly disabled by policy.
    if not allow_cortex:
        return _finalize_result(
            _base_result(
                fix_type="no_fix",
                fix_detail="No deterministic fix matched and Cortex fallback is disabled by policy.",
                risk_tier="high",
                similar_cases=[],
                confidence=0.1,
                patch=None,
                validation_suite=None,
                decision_reasons=["cortex_disabled_by_policy"],
            ),
            tier_path=tier_path,
            extra_reasons=decision_reasons,
        )

    # Tier 3: Cortex AI fallback
    result = _try_cortex_fallback(
        event=event,
        trace_result=trace_result,
        cortex_client=cortex_client,
    )
    tier_path.append("cortex_ai")
    return _finalize_result(result, tier_path=tier_path, extra_reasons=decision_reasons)


def _base_result(
    *,
    fix_type: str,
    fix_detail: str,
    risk_tier: str,
    similar_cases: Optional[List[Dict[str, Any]]] = None,
    confidence: float = 0.0,
    patch: Optional[Dict[str, Any]] = None,
    validation_suite: Optional[Dict[str, Any]] = None,
    decision_reasons: Optional[List[str]] = None,
) -> Dict[str, Any]:
    return {
        "fix_type": fix_type,
        "fix_detail": fix_detail,
        "risk_tier": risk_tier,
        "similar_cases": list(similar_cases or []),
        "confidence": round(float(confidence), 4),
        "patch": patch,
        "validation_suite": validation_suite,
        "tier_path": [],
        "decision_reasons": list(decision_reasons or []),
        "requires_human_approval": human_approval_required(risk_tier, fix_type=fix_type),
    }


def _finalize_result(
    result: Dict[str, Any],
    *,
    tier_path: List[str],
    extra_reasons: Optional[List[str]] = None,
) -> Dict[str, Any]:
    out = dict(result)
    out["tier_path"] = list(tier_path)
    reasons: List[str] = []
    reasons.extend(list(extra_reasons or []))
    reasons.extend(list(out.get("decision_reasons", []) or []))
    out["decision_reasons"] = reasons
    out["requires_human_approval"] = bool(
        out.get(
            "requires_human_approval",
            human_approval_required(out.get("risk_tier", "high"), fix_type=out.get("fix_type")),
        )
    )

    # Ensure full contract is always present.
    for key, default in (
        ("fix_type", "no_fix"),
        ("fix_detail", ""),
        ("risk_tier", "high"),
        ("similar_cases", []),
        ("confidence", 0.0),
        ("patch", None),
        ("validation_suite", None),
    ):
        if key not in out:
            out[key] = default
    return out


def _try_trust_replay(trace_result: Dict[str, Any]) -> Dict[str, Any]:
    """Tier 1: Replay a matching trust case if confidence >= threshold."""
    suspect_nodes = trace_result.get("suspect_nodes", [])
    similar_cases: List[Dict[str, Any]] = []

    for node in suspect_nodes:
        if node.get("source") == "trust_case":
            root_cause = node.get("root_cause", "")
            case_id = node.get("case_id", "")
            if root_cause and root_cause != "unknown":
                similar_cases.append(
                    {
                        "case_id": case_id,
                        "root_cause": root_cause,
                    }
                )

    trace_confidence = trace_result.get("confidence", 0.0)

    if should_use_trust_replay(trace_confidence, len(similar_cases)):
        return _base_result(
            fix_type="trust_replay",
            fix_detail=(
                f"Replay proven fix from {len(similar_cases)} similar case(s): "
                f"{similar_cases[0].get('root_cause', '')}"
            ),
            risk_tier="low",
            similar_cases=similar_cases,
            confidence=trace_confidence,
            patch=None,
            validation_suite=None,
            decision_reasons=["trust_replay_match"],
        )

    reason = "trust_replay_low_confidence" if similar_cases else "trust_replay_no_similar_cases"
    return _base_result(
        fix_type="_passthrough",
        fix_detail="",
        risk_tier="low",
        similar_cases=similar_cases,
        confidence=trace_confidence,
        patch=None,
        validation_suite=None,
        decision_reasons=[reason],
    )


def _try_relationship_rag(
    event: Dict[str, Any],
    trace_result: Dict[str, Any],
    samples: Optional[Dict] = None,
    column_metadata: Optional[Dict] = None,
    erp_type: str = "UNKNOWN",
) -> Dict[str, Any]:
    """Tier 2: Use RelationshipInferer + DimensionDetector + DMSpecGenerator."""
    if not samples:
        return _base_result(
            fix_type="_passthrough",
            fix_detail="",
            risk_tier="medium",
            similar_cases=[],
            confidence=0.0,
            patch=None,
            validation_suite=None,
            decision_reasons=["relationship_rag_no_samples"],
        )

    try:
        from src.data_modeling.relationship_inferer import infer_relationships
        from src.data_modeling.dimension_detector import DimensionDetector
        from src.data_modeling.spec_generator import DMSpecGenerator
        from src.data_modeling.focus_config import InferenceConfig
    except ImportError as exc:
        logger.warning("Tier 2 modules unavailable: %s", exc)
        return _base_result(
            fix_type="_passthrough",
            fix_detail="",
            risk_tier="medium",
            similar_cases=[],
            confidence=0.0,
            patch=None,
            validation_suite=None,
            decision_reasons=["relationship_rag_modules_unavailable"],
        )

    try:
        # Step 1: Infer relationships from samples
        relationships = infer_relationships(samples, config=InferenceConfig())

        if not relationships:
            return _base_result(
                fix_type="_passthrough",
                fix_detail="",
                risk_tier="medium",
                similar_cases=[],
                confidence=0.0,
                patch=None,
                validation_suite=None,
                decision_reasons=["relationship_rag_no_relationships"],
            )

        # Step 2: Classify tables as dim/fact
        detector = DimensionDetector()
        classification = detector.classify_tables(relationships)

        # Step 3: Generate target spec
        generator = DMSpecGenerator()
        specs = generator.generate(
            relationships=relationships,
            classification=classification,
            column_metadata=column_metadata,
            erp_type=erp_type,
        )

        # Step 4: Build structured patch by comparing spec against trace lineage
        lineage_path = trace_result.get("lineage_path", [])
        tables_in_lineage = set(lineage_path) if lineage_path else set()

        spec_changes = []
        for spec in specs:
            spec_name = getattr(spec, "name", str(spec))
            spec_columns = getattr(spec, "columns", [])
            col_info = []
            for col in spec_columns:
                col_name = getattr(col, "name", str(col))
                col_kind = getattr(col, "kind", "unknown")
                col_info.append({"name": col_name, "kind": str(col_kind)})

            spec_changes.append(
                {
                    "table": spec_name,
                    "action": "validate" if spec_name in tables_in_lineage else "add",
                    "columns": col_info,
                }
            )

        patch = {
            "tables_affected": len(spec_changes),
            "spec_changes": spec_changes,
            "relationships_added": len(relationships),
        }

        # Step 5: Generate quality expectations if possible
        validation_suite = _generate_validation_suite(classification, column_metadata)

        confidence = min(0.8, 0.4 + 0.05 * len(relationships))

        return _base_result(
            fix_type="relationship_rag",
            fix_detail=(
                f"Schema patch: {len(spec_changes)} table(s), "
                f"{len(relationships)} FK relationship(s) discovered"
            ),
            risk_tier="medium",
            similar_cases=[],
            confidence=confidence,
            patch=patch,
            validation_suite=validation_suite,
            decision_reasons=["relationship_rag_inferred_patch"],
        )
    except Exception as exc:
        logger.warning("Tier 2 relationship RAG failed: %s", exc)
        return _base_result(
            fix_type="_passthrough",
            fix_detail="",
            risk_tier="medium",
            similar_cases=[],
            confidence=0.0,
            patch=None,
            validation_suite=None,
            decision_reasons=["relationship_rag_exception"],
        )


def _generate_validation_suite(
    classification: Dict[str, str],
    column_metadata: Optional[Dict] = None,
) -> Optional[Dict]:
    """Generate quality expectations from classification results."""
    try:
        from src.data_quality.classification_bridge import ClassificationExpectationBridge

        bridge = ClassificationExpectationBridge()
        # Build a flat column classification for the first table that has columns
        if column_metadata:
            for table_name, cols in column_metadata.items():
                col_classifications = {}
                for col in cols:
                    col_name = col.get("name", "") if isinstance(col, dict) else str(col)
                    # Use table classification as a hint
                    col_classifications[col_name] = classification.get(table_name, "unknown")
                if col_classifications:
                    suite = bridge.generate_suite(
                        suite_name=f"fix_validation_{table_name}",
                        classifications=col_classifications,
                        table_name=table_name,
                    )
                    return {
                        "suite_name": suite.name,
                        "expectations_count": len(suite.expectations),
                        "table": table_name,
                    }
    except Exception as exc:
        logger.debug("Validation suite generation skipped: %s", exc)
    return None


def _try_cortex_fallback(
    event: Dict[str, Any],
    trace_result: Dict[str, Any],
    cortex_client: Optional[Any] = None,
) -> Dict[str, Any]:
    """Tier 3: Use Cortex AI for fix recommendation."""
    if cortex_client is None:
        return _base_result(
            fix_type="no_fix",
            fix_detail="No matching fix found. Manual investigation recommended.",
            risk_tier="high",
            similar_cases=[],
            confidence=0.1,
            patch=None,
            validation_suite=None,
            decision_reasons=["cortex_client_unavailable"],
        )

    try:
        metric = event.get("metric", "unknown")
        symptom = event.get("symptom", "unknown")
        source_system = event.get("source_system", "unknown")
        lineage_path = trace_result.get("lineage_path", [])
        suspect_nodes = trace_result.get("suspect_nodes", [])

        suspect_summary = "; ".join(
            f"{n.get('source', '?')}: {n.get('root_cause', n.get('name', '?'))}" for n in suspect_nodes[:5]
        )

        prompt = (
            "A data discrepancy has been detected.\n"
            f"Metric: {metric}\n"
            f"Symptom: {symptom}\n"
            f"Source system: {source_system}\n"
            f"Lineage path: {' -> '.join(lineage_path) if lineage_path else 'unknown'}\n"
            f"Suspect nodes: {suspect_summary or 'none'}\n\n"
            "Propose a concrete fix for this discrepancy. "
            "Be specific about which tables, columns, or mappings to change."
        )

        result = cortex_client.complete(prompt)
        fix_text = ""
        if hasattr(result, "result"):
            fix_text = str(result.result)
        elif isinstance(result, dict):
            fix_text = str(result.get("result", result))
        else:
            fix_text = str(result)

        return _base_result(
            fix_type="cortex_ai",
            fix_detail=fix_text[:2000],
            risk_tier="high",
            similar_cases=[],
            confidence=0.5,
            patch=None,
            validation_suite=None,
            decision_reasons=["cortex_ai_fallback_used"],
        )
    except Exception as exc:
        logger.warning("Tier 3 Cortex fallback failed: %s", exc)
        return _base_result(
            fix_type="no_fix",
            fix_detail=f"Cortex AI failed: {exc}",
            risk_tier="high",
            similar_cases=[],
            confidence=0.0,
            patch=None,
            validation_suite=None,
            decision_reasons=["cortex_ai_error"],
        )
