"""
Diff Analyzer — blast radius of a set of changed files.

Used by:
  kodara diff <branch>   — compare current branch to target
  kodara review          — analyze staged/unstaged changes before commit

Algorithm:
  1. Get changed file list from git
  2. Run impact analysis on each changed file
  3. Aggregate: total affected, risk distribution, critical files
"""

from __future__ import annotations

import subprocess
from dataclasses import dataclass, field
from pathlib import Path

from ..memory.store import KodaraIndex
from ..impact.analyzer import analyze, ImpactResult


@dataclass
class FileChange:
    path: str
    status: str          # M=modified, A=added, D=deleted, R=renamed
    impact: ImpactResult | None = None
    in_index: bool = True


@dataclass
class DiffResult:
    base: str                        # branch/ref we compared against
    changed_files: list[FileChange]
    total_affected_modules: int
    risk_distribution: dict[str, int]  # {CRITICAL:1, HIGH:2, ...}
    overall_risk: str
    summary: str


def analyze_diff(
    index: KodaraIndex,
    repo_root: str,
    base: str = "HEAD",
    staged_only: bool = False,
) -> DiffResult:
    """
    Analyze impact of all changed files vs base ref.

    Args:
        index:       Loaded KodaraIndex
        repo_root:   Git repo root path
        base:        Branch/commit to compare against (default: HEAD = staged changes)
        staged_only: If True, only analyze staged changes (pre-commit mode)
    """
    changed = _get_changed_files(repo_root, base, staged_only)
    if not changed:
        return DiffResult(
            base=base,
            changed_files=[],
            total_affected_modules=0,
            risk_distribution={},
            overall_risk="NONE",
            summary="No changed files found.",
        )

    all_affected: set[str] = set()
    risk_dist: dict[str, int] = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0, "NONE": 0}

    file_changes: list[FileChange] = []
    for path, status in changed:
        fc = FileChange(path=path, status=status)
        if status != "D":  # skip deleted files
            try:
                result = analyze(index, path)
                fc.impact = result
                all_affected.update(m.path for m in result.direct + result.indirect)
                risk_dist[result.risk_level] = risk_dist.get(result.risk_level, 0) + 1
            except ValueError:
                fc.in_index = False
        file_changes.append(fc)

    overall = _overall_risk(risk_dist)
    summary = _build_summary(file_changes, all_affected, risk_dist, base)

    return DiffResult(
        base=base,
        changed_files=file_changes,
        total_affected_modules=len(all_affected),
        risk_distribution=risk_dist,
        overall_risk=overall,
        summary=summary,
    )


# ── Git helpers ───────────────────────────────────────────────────────────────


def _get_changed_files(
    repo_root: str, base: str, staged_only: bool
) -> list[tuple[str, str]]:
    """Return [(relative_path, status)] for changed files."""
    try:
        if staged_only or base == "HEAD":
            # Staged changes only (for pre-commit review)
            cmd = ["git", "diff", "--cached", "--name-status"]
        else:
            # Compare current working tree against branch/commit
            cmd = ["git", "diff", base, "--name-status"]

        result = subprocess.run(
            cmd, cwd=repo_root, capture_output=True, text=True, timeout=15
        )

        if result.returncode != 0:
            # Fallback: working tree changes
            result = subprocess.run(
                ["git", "status", "--porcelain"],
                cwd=repo_root, capture_output=True, text=True, timeout=10
            )
            files = []
            for line in result.stdout.strip().splitlines():
                if len(line) >= 4:
                    status = line[1].strip() or line[0].strip()
                    path = line[3:].strip()
                    files.append((path, status or "M"))
            return files

        files = []
        for line in result.stdout.strip().splitlines():
            parts = line.split("\t", 1)
            if len(parts) == 2:
                status, path = parts
                files.append((path.strip(), status[0]))
        return files

    except Exception:
        return []


def get_staged_files(repo_root: str) -> list[tuple[str, str]]:
    """Return staged files for pre-commit review."""
    return _get_changed_files(repo_root, "HEAD", staged_only=True)


def get_unstaged_files(repo_root: str) -> list[tuple[str, str]]:
    """Return modified but unstaged files."""
    try:
        result = subprocess.run(
            ["git", "diff", "--name-status"],
            cwd=repo_root, capture_output=True, text=True, timeout=10
        )
        files = []
        for line in result.stdout.strip().splitlines():
            parts = line.split("\t", 1)
            if len(parts) == 2:
                files.append((parts[1].strip(), parts[0][0]))
        return files
    except Exception:
        return []


# ── Risk aggregation ──────────────────────────────────────────────────────────


def _overall_risk(dist: dict[str, int]) -> str:
    if dist.get("CRITICAL", 0) > 0:
        return "CRITICAL"
    if dist.get("HIGH", 0) > 0:
        return "HIGH"
    if dist.get("MEDIUM", 0) > 0:
        return "MEDIUM"
    if dist.get("LOW", 0) > 0:
        return "LOW"
    return "NONE"


def _build_summary(
    changes: list[FileChange],
    all_affected: set[str],
    risk_dist: dict[str, int],
    base: str,
) -> str:
    in_index = [c for c in changes if c.in_index]
    critical = [c for c in in_index if c.impact and c.impact.risk_level == "CRITICAL"]
    high = [c for c in in_index if c.impact and c.impact.risk_level == "HIGH"]

    parts = [f"{len(changes)} file(s) changed vs {base}."]
    if all_affected:
        parts.append(f"Total blast radius: {len(all_affected)} modules.")
    if critical:
        names = ", ".join(c.impact.target_module for c in critical[:2])
        parts.append(f"CRITICAL: {names}.")
    elif high:
        names = ", ".join(c.impact.target_module for c in high[:2])
        parts.append(f"HIGH risk: {names}.")
    return " ".join(parts)
