"""SBFL applied to I/O records.

Treats each I/O operation (HTTP call, SQL query, Redis command, gRPC call)
as a "location" and ranks which operations are most correlated with
test failures using Spectrum-Based Fault Localization.
"""

from __future__ import annotations

import math
import re
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Dict, List, Set, Tuple

if TYPE_CHECKING:
    from autodebug.detection.transcript_analyzer import Transcript


@dataclass
class IOOperation:
    """An I/O operation treated as a suspiciousness location."""
    operation_type: str  # "http", "sql", "redis", "grpc"
    canonical_key: str   # Normalized identifier
    score: float = 0.0
    rank: int = 0
    ef: int = 0  # Executed in failing
    ep: int = 0  # Executed in passing
    nf: int = 0  # Not executed in failing
    np_: int = 0  # Not executed in passing (np_ to avoid shadowing builtin)


@dataclass
class IOSBFLResult:
    """Result of I/O SBFL analysis."""
    suspicious_operations: List[IOOperation] = field(default_factory=list)
    total_passed: int = 0
    total_failed: int = 0


def _normalize_url(url: str) -> str:
    """Normalize URL for comparison (remove UUIDs, numbers)."""
    normalized = re.sub(
        r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
        ":id",
        url,
        flags=re.IGNORECASE,
    )
    normalized = re.sub(r"/\d+(?=/|$)", "/:num", normalized)
    return normalized


def _normalize_sql(sql: str) -> str:
    """Normalize SQL for comparison."""
    normalized = re.sub(r"'[^']*'", "'?'", sql)
    normalized = re.sub(r"\b\d+\b", "?", normalized)
    normalized = re.sub(
        r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
        "?",
        normalized,
        flags=re.IGNORECASE,
    )
    normalized = re.sub(r"\s+", " ", normalized)
    return normalized.strip()[:200]


def extract_operations(transcript: Transcript) -> Set[str]:
    """Extract normalized I/O operation keys from a transcript."""
    ops: Set[str] = set()

    for r in transcript.http_records:
        key = f"http:{r.method}:{_normalize_url(r.url_canonical)}"
        ops.add(key)

    for r in transcript.sql_records:
        key = f"sql:{_normalize_sql(r.sql_canonical)}"
        ops.add(key)

    for r in getattr(transcript, "redis_records", []):
        key = f"redis:{r.command}:{r.key}"
        ops.add(key)

    for r in getattr(transcript, "grpc_records", []):
        key = f"grpc:{r.service}/{r.method}"
        ops.add(key)

    return ops


def _ochiai(ef: int, ep: int, total_failed: int) -> float:
    """Ochiai suspiciousness formula."""
    denominator = math.sqrt(total_failed * (ef + ep))
    if denominator == 0:
        return 0.0
    return ef / denominator


class IOSBFLAnalyzer:
    """Apply SBFL to I/O operations across pass/fail runs."""

    def analyze(
        self,
        runs: List[Tuple[Transcript, bool]],
        max_results: int = 50,
    ) -> IOSBFLResult:
        """Analyze multiple runs to find suspicious I/O operations.

        Args:
            runs: List of (transcript, passed) tuples
            max_results: Maximum suspicious operations to return
        """
        total_passed = sum(1 for _, passed in runs if passed)
        total_failed = sum(1 for _, passed in runs if not passed)

        if total_failed == 0:
            return IOSBFLResult(total_passed=total_passed, total_failed=0)

        # Collect all operations and their presence in pass/fail runs
        op_stats: Dict[str, Dict[str, int]] = {}  # key -> {ef, ep}

        for transcript, passed in runs:
            ops = extract_operations(transcript)
            for op_key in ops:
                if op_key not in op_stats:
                    op_stats[op_key] = {"ef": 0, "ep": 0}
                if passed:
                    op_stats[op_key]["ep"] += 1
                else:
                    op_stats[op_key]["ef"] += 1

        # Compute suspiciousness scores
        suspicious: List[IOOperation] = []
        for op_key, stats in op_stats.items():
            ef = stats["ef"]
            ep = stats["ep"]
            nf = total_failed - ef
            np_ = total_passed - ep

            score = _ochiai(ef, ep, total_failed)

            if score > 0:
                parts = op_key.split(":", 1)
                op_type = parts[0] if parts else "unknown"
                canonical = parts[1] if len(parts) > 1 else op_key

                suspicious.append(IOOperation(
                    operation_type=op_type,
                    canonical_key=canonical,
                    score=score,
                    ef=ef,
                    ep=ep,
                    nf=nf,
                    np_=np_,
                ))

        suspicious.sort(key=lambda o: o.score, reverse=True)
        for i, op in enumerate(suspicious[:max_results]):
            op.rank = i + 1

        return IOSBFLResult(
            suspicious_operations=suspicious[:max_results],
            total_passed=total_passed,
            total_failed=total_failed,
        )


def rank_suspicious_io(
    passing_transcripts: List[Transcript],
    failing_transcripts: List[Transcript],
    max_results: int = 50,
) -> IOSBFLResult:
    """Convenience function to rank suspicious I/O operations."""
    runs = [(t, True) for t in passing_transcripts] + [(t, False) for t in failing_transcripts]
    analyzer = IOSBFLAnalyzer()
    return analyzer.analyze(runs, max_results)
