"""LLM-augmented root cause analysis.

Takes structured TranscriptInsights and generates plain-English
root cause explanations, suggested fixes, and confidence scores.
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Callable, List, Optional

if TYPE_CHECKING:
    from autodebug.detection.transcript_analyzer import Transcript, TranscriptInsight


@dataclass
class RootCauseExplanation:
    """LLM-generated root cause explanation."""
    summary: str
    detailed_explanation: str
    suggested_fix: str
    confidence: float  # 0.0 to 1.0
    related_insights: List[str] = field(default_factory=list)


@dataclass
class LLMAnalysisResult:
    """Complete LLM analysis output."""
    explanations: List[RootCauseExplanation] = field(default_factory=list)
    overall_summary: str = ""


def build_analysis_prompt(
    insights: List[TranscriptInsight],
    transcript: Optional[Transcript] = None,
    source_code: Optional[str] = None,
) -> str:
    """Build a prompt for LLM root cause analysis from insights."""
    parts = [
        "Analyze the following issues detected in a program execution and provide root cause analysis.\n",
        "For each issue, explain:\n"
        "1. WHY it happened (root cause)\n"
        "2. HOW to fix it (specific code-level suggestion)\n"
        "3. Your confidence level (0-100%)\n\n",
        "## Detected Issues\n",
    ]

    for i, insight in enumerate(insights, 1):
        parts.append(f"\n### Issue {i}: {insight.category} (severity {insight.severity}/5)")
        parts.append(f"Description: {insight.description}")
        if insight.evidence:
            parts.append("Evidence:")
            for k, v in insight.evidence.items():
                parts.append(f"  - {k}: {v}")
        if insight.suggested_investigation:
            parts.append(f"Investigation hint: {insight.suggested_investigation}")

    if transcript:
        parts.append("\n## Execution Context")
        if transcript.http_records:
            parts.append(f"HTTP calls: {len(transcript.http_records)}")
            errors = [r for r in transcript.http_records if r.status >= 400]
            if errors:
                parts.append(f"HTTP errors: {len(errors)} ({', '.join(f'{r.status} {r.url_canonical}' for r in errors[:3])})")
        if transcript.sql_records:
            parts.append(f"SQL queries: {len(transcript.sql_records)}")
        if transcript.redis_records:
            parts.append(f"Redis commands: {len(transcript.redis_records)}")
        if transcript.grpc_records:
            parts.append(f"gRPC calls: {len(transcript.grpc_records)}")
        if transcript.exception:
            parts.append(f"Exception: {transcript.exception.type}: {transcript.exception.message[:300]}")

    if source_code:
        parts.append(f"\n## Relevant Source Code\n```\n{source_code[:2000]}\n```")

    parts.append(
        "\n## Output Format\n"
        "For each issue, respond with:\n"
        "- Root cause (1-2 sentences)\n"
        "- Suggested fix (specific code change)\n"
        "- Confidence (0-100%)\n"
    )

    return "\n".join(parts)


class LLMAnalyzer:
    """Generate root cause explanations using an LLM.

    Accepts a callable `llm_fn(prompt) -> str` for flexibility:
    can be backed by any LLM provider (Anthropic, OpenAI, local, etc.).
    """

    def __init__(
        self,
        llm_fn: Optional[Callable[[str], str]] = None,
    ):
        self.llm_fn = llm_fn

    def analyze(
        self,
        insights: List[TranscriptInsight],
        transcript: Optional[Transcript] = None,
        source_code: Optional[str] = None,
    ) -> LLMAnalysisResult:
        """Generate LLM-powered root cause explanations.

        If no llm_fn is configured, falls back to rule-based explanations.
        """
        if not insights:
            return LLMAnalysisResult(overall_summary="No issues detected.")

        if self.llm_fn is not None:
            return self._analyze_with_llm(insights, transcript, source_code)

        return self._analyze_rule_based(insights)

    def _analyze_with_llm(
        self,
        insights: List[TranscriptInsight],
        transcript: Optional[Transcript],
        source_code: Optional[str],
    ) -> LLMAnalysisResult:
        prompt = build_analysis_prompt(insights, transcript, source_code)
        raw = self.llm_fn(prompt)
        return self._parse_llm_response(raw, insights)

    def _parse_llm_response(
        self,
        raw: str,
        insights: List[TranscriptInsight],
    ) -> LLMAnalysisResult:
        """Parse LLM response into structured explanations.

        Best-effort parsing: extracts what it can from free-form text.
        """
        explanations: List[RootCauseExplanation] = []

        # Split response into sections per issue
        sections = raw.split("###")
        if len(sections) <= 1:
            sections = raw.split("Issue")

        for section in sections:
            section = section.strip()
            if not section or len(section) < 20:
                continue

            # Extract confidence
            confidence = 0.5
            for line in section.split("\n"):
                lower = line.lower()
                if "confidence" in lower:
                    match = re.search(r"(\d+)\s*%", line)
                    if match:
                        confidence = int(match.group(1)) / 100.0

            lines = [l.strip() for l in section.split("\n") if l.strip()]
            summary = lines[0] if lines else section[:100]
            detailed = "\n".join(lines[1:]) if len(lines) > 1 else summary

            # Try to find fix suggestion
            fix = ""
            for line in lines:
                lower = line.lower()
                if any(kw in lower for kw in ["fix", "suggest", "change", "replace", "add", "use"]):
                    fix = line
                    break

            explanations.append(
                RootCauseExplanation(
                    summary=summary[:200],
                    detailed_explanation=detailed[:1000],
                    suggested_fix=fix[:500],
                    confidence=confidence,
                )
            )

        overall = f"Found {len(explanations)} root cause(s) for {len(insights)} detected issue(s)."

        return LLMAnalysisResult(
            explanations=explanations,
            overall_summary=overall,
        )

    def _analyze_rule_based(
        self,
        insights: List[TranscriptInsight],
    ) -> LLMAnalysisResult:
        """Fallback: generate explanations using rules when no LLM is available."""
        explanations: List[RootCauseExplanation] = []

        for insight in insights:
            explanation = _RULE_BASED_EXPLANATIONS.get(
                insight.category,
                _default_explanation,
            )(insight)
            explanations.append(explanation)

        severity_max = max((i.severity for i in insights), default=0)
        overall = (
            f"Detected {len(insights)} issue(s) "
            f"(max severity {severity_max}/5). "
            f"Generated {len(explanations)} rule-based explanation(s)."
        )

        return LLMAnalysisResult(
            explanations=explanations,
            overall_summary=overall,
        )


def _default_explanation(insight: TranscriptInsight) -> RootCauseExplanation:
    return RootCauseExplanation(
        summary=insight.description,
        detailed_explanation=f"Category: {insight.category}. {insight.suggested_investigation}",
        suggested_fix=insight.suggested_investigation,
        confidence=0.5,
        related_insights=[insight.category],
    )


def _explain_http_error(insight: TranscriptInsight) -> RootCauseExplanation:
    status = insight.evidence.get("status", "unknown")
    url = insight.evidence.get("url", "unknown")
    return RootCauseExplanation(
        summary=f"HTTP {status} from {url} indicates the API returned an error",
        detailed_explanation=(
            f"The request to {url} returned HTTP {status}. "
            f"This could be caused by invalid request data, "
            f"missing authentication, or a server-side bug."
        ),
        suggested_fix=insight.suggested_investigation,
        confidence=0.8,
        related_insights=["http_error"],
    )


def _explain_sql_error(insight: TranscriptInsight) -> RootCauseExplanation:
    pgcode = insight.evidence.get("pgcode", "")
    query = insight.evidence.get("query", "")
    return RootCauseExplanation(
        summary=f"SQL error (pgcode: {pgcode}) on query: {query[:60]}",
        detailed_explanation=(
            f"The database rejected the query. pgcode {pgcode} typically means "
            f"a constraint violation, missing table/column, or connection issue."
        ),
        suggested_fix=insight.suggested_investigation,
        confidence=0.85,
        related_insights=["sql_error"],
    )


def _explain_cross_protocol_chain(insight: TranscriptInsight) -> RootCauseExplanation:
    chain = insight.evidence.get("chain", [])
    protocols = insight.evidence.get("protocols_affected", [])
    return RootCauseExplanation(
        summary=f"Cascading failure across {', '.join(protocols)}",
        detailed_explanation=(
            f"Errors cascaded across {len(protocols)} protocols. "
            f"The root cause is likely the first error in the chain. "
            f"Fix that to prevent downstream failures."
        ),
        suggested_fix="Fix the earliest error in the chain to prevent cascading failures",
        confidence=0.7,
        related_insights=["cross_protocol_failure_chain"],
    )


def _explain_n_plus_one(insight: TranscriptInsight) -> RootCauseExplanation:
    count = insight.evidence.get("count", 0)
    return RootCauseExplanation(
        summary=f"N+1 query problem: {count} similar queries executed sequentially",
        detailed_explanation=(
            f"The code is executing {count} nearly identical SQL queries in a loop. "
            f"This is a common performance anti-pattern."
        ),
        suggested_fix="Use a single query with JOIN or IN clause, or use batch loading (e.g., SQLAlchemy's selectinload)",
        confidence=0.9,
        related_insights=["n_plus_one"],
    )


def _explain_auth_failure(insight: TranscriptInsight) -> RootCauseExplanation:
    url = insight.evidence.get("url", "unknown")
    return RootCauseExplanation(
        summary=f"Authentication failed when calling {url}",
        detailed_explanation="The request was rejected due to invalid or expired authentication credentials.",
        suggested_fix="Check that auth tokens are valid, not expired, and properly attached to requests",
        confidence=0.85,
        related_insights=["auth_failure"],
    )


def _explain_timeout(insight: TranscriptInsight) -> RootCauseExplanation:
    duration = insight.evidence.get("duration_ms", "unknown")
    return RootCauseExplanation(
        summary=f"Operation took {duration}ms, approaching or exceeding timeout",
        detailed_explanation="A slow operation is at risk of timing out, or has already timed out.",
        suggested_fix="Optimize the slow operation, increase timeout, or add retry with backoff",
        confidence=0.8,
        related_insights=["timeout_risk", "slow_operation_caused_timeout"],
    )


def _explain_redis_error(insight: TranscriptInsight) -> RootCauseExplanation:
    cmd = insight.evidence.get("redis_command", "unknown")
    return RootCauseExplanation(
        summary=f"Redis {cmd} command failed",
        detailed_explanation="A Redis operation failed, possibly due to connection issues, memory limits, or wrong key type.",
        suggested_fix="Check Redis server health, connection settings, and key types",
        confidence=0.8,
        related_insights=["redis_error"],
    )


def _explain_grpc_error(insight: TranscriptInsight) -> RootCauseExplanation:
    svc = insight.evidence.get("grpc_service", "unknown")
    status = insight.evidence.get("grpc_status", "unknown")
    return RootCauseExplanation(
        summary=f"gRPC call to {svc} failed with status {status}",
        detailed_explanation="A gRPC service call returned an error status code.",
        suggested_fix="Check the gRPC server logs, verify service health, and validate request parameters",
        confidence=0.75,
        related_insights=["grpc_error"],
    )


def _explain_sensitive_data(insight: TranscriptInsight) -> RootCauseExplanation:
    data_type = insight.evidence.get("data_type", "sensitive data")
    return RootCauseExplanation(
        summary=f"Sensitive data ({data_type}) exposed in HTTP traffic",
        detailed_explanation="Personally identifiable information or secrets were found in request/response bodies.",
        suggested_fix="Mask or remove sensitive data from API responses; use encryption for sensitive fields",
        confidence=0.9,
        related_insights=["sensitive_data_exposure"],
    )


_RULE_BASED_EXPLANATIONS = {
    "http_error": _explain_http_error,
    "unique_violation": _explain_sql_error,
    "foreign_key_violation": _explain_sql_error,
    "not_null_violation": _explain_sql_error,
    "undefined_table": _explain_sql_error,
    "undefined_column": _explain_sql_error,
    "sql_error": _explain_sql_error,
    "deadlock_detected": _explain_sql_error,
    "sql_caused_exception": _explain_sql_error,
    "cross_protocol_failure_chain": _explain_cross_protocol_chain,
    "cross_protocol_chain": _explain_cross_protocol_chain,
    "n_plus_one": _explain_n_plus_one,
    "auth_failure": _explain_auth_failure,
    "auth_caused_exception": _explain_auth_failure,
    "permission_denied": _explain_auth_failure,
    "timeout_risk": _explain_timeout,
    "slow_operation_caused_timeout": _explain_timeout,
    "slow_http": _explain_timeout,
    "slow_query": _explain_timeout,
    "redis_error": _explain_redis_error,
    "redis_caused_exception": _explain_redis_error,
    "grpc_error": _explain_grpc_error,
    "grpc_caused_exception": _explain_grpc_error,
    "http_caused_exception": _explain_http_error,
    "sensitive_data_exposure": _explain_sensitive_data,
    "sql_injection_risk": _explain_sensitive_data,
}
