#!/usr/bin/env python3
"""Regenerate all Overton window reports in correct dependency order.

Usage:
    uv run python analysis/right_wing/build_all_reports.py
    uv run python analysis/right_wing/build_all_reports.py --skip-llm
"""

from __future__ import annotations

import argparse
import logging
import subprocess
import sys
import time
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))

from analysis.right_wing.common import REPORTS_DIR

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%H:%M:%S",
)
logger = logging.getLogger("build_all_reports")

SCRIPT_DIR = ROOT / "analysis" / "right_wing"

PHASE_1_SCRIPTS = [
    "overton_breakpoint_analysis.py",
    "temporal_trajectory.py",
    "causal_timing.py",
    "party_differentiation.py",
    "voting_margin.py",
    "left_wing_response.py",
    "success_correlation.py",
    "overton_svd_drift.py",
    "svd_trajectory_viz.py",
]

PHASE_1_OUTPUTS = [
    "breakpoint_analysis.md",
    "breakpoint_figure_1.png",
    "breakpoint_figure_2.png",
    "breakpoint_figure_3.png",
    "breakpoint_figure_4.png",
    "temporal_trajectory.md",
    "temporal_trajectory_figure.png",
    "causal_timing.md",
    "causal_timing_figure.png",
    "party_differentiation.md",
    "party_differentiation_figure.png",
    "voting_margin.md",
    "voting_margin_figure.png",
    "left_wing_response.md",
    "left_wing_response_figure.png",
    "success_correlation.md",
    "svd_drift_chart.png",
    "svd_stability_report.md",
    "svd_trajectory_figure.png",
]

PHASE_2_SCRIPTS = [
    "extremity_2d_temporal.py",
    "predictive_model.py",
    "mechanism_classification.py",
]

PHASE_2_OUTPUTS = [
    "extremity_2d_temporal.md",
    "extremity_2d_temporal_figure.png",
    "predictive_model.md",
    "predictive_model_figure.png",
    "mechanism_classification.md",
]

PHASE_3_SCRIPTS = [
    "derive_categories.py",
]


def _script_path(name: str) -> str:
    return str(SCRIPT_DIR / name)


def _run_script(name: str) -> bool:
    """Run a single script via subprocess. Returns True on success."""
    logger.info("Running %s ...", name)
    t0 = time.perf_counter()
    try:
        subprocess.run(
            [sys.executable, _script_path(name)],
            cwd=str(ROOT),
            check=True,
            capture_output=True,
            text=True,
        )
        elapsed = time.perf_counter() - t0
        logger.info("Finished %s (%.1fs)", name, elapsed)
        return True
    except subprocess.CalledProcessError as exc:
        elapsed = time.perf_counter() - t0
        logger.error("Script %s failed after %.1fs (rc=%d)", name, elapsed, exc.returncode)
        if exc.stdout:
            for line in exc.stdout.strip().splitlines():
                logger.error("  stdout: %s", line)
        if exc.stderr:
            for line in exc.stderr.strip().splitlines():
                logger.error("  stderr: %s", line)
        return False


def _verify_outputs(files: list[str]) -> list[str]:
    """Return list of expected output files that are missing."""
    missing = []
    for f in files:
        if not (REPORTS_DIR / f).exists():
            missing.append(f)
    return missing


def _run_phase(
    phase_label: str, scripts: list[str], expected_outputs: list[str]
) -> tuple[list[str], list[str]]:
    """Run a list of scripts and verify outputs. Returns (succeeded, failed)."""
    logger.info("=" * 50)
    logger.info("Phase %s", phase_label)
    logger.info("=" * 50)

    succeeded = []
    failed = []

    for script in scripts:
        ok = _run_script(script)
        if ok:
            succeeded.append(script)
        else:
            failed.append(script)

    missing = _verify_outputs(expected_outputs)
    if missing:
        logger.warning(
            "Phase %s: %d expected output(s) missing after run:\n  %s",
            phase_label,
            len(missing),
            "\n  ".join(missing),
        )
    else:
        logger.info("Phase %s: all expected outputs present.", phase_label)

    return succeeded, failed


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Regenerate all Overton window reports in dependency order."
    )
    parser.add_argument(
        "--skip-llm",
        action="store_true",
        help="Skip LLM-dependent phase (derive_categories.py)",
    )
    args = parser.parse_args()

    REPORTS_DIR.mkdir(parents=True, exist_ok=True)

    all_succeeded: list[str] = []
    all_failed: list[str] = []
    t_start = time.perf_counter()

    # Phase 1: database-dependent (no LLM)
    s, f = _run_phase("1 — database-dependent", PHASE_1_SCRIPTS, PHASE_1_OUTPUTS)
    all_succeeded.extend(s)
    all_failed.extend(f)

    # Phase 2: 2D extremity-dependent (no LLM)
    s, f = _run_phase("2 — 2D extremity-dependent", PHASE_2_SCRIPTS, PHASE_2_OUTPUTS)
    all_succeeded.extend(s)
    all_failed.extend(f)

    # Phase 3: LLM-dependent
    if not args.skip_llm:
        s, f = _run_phase("3 — LLM-dependent", PHASE_3_SCRIPTS, [])
        all_succeeded.extend(s)
        all_failed.extend(f)
    else:
        logger.info("Skipping LLM-dependent phase (--skip-llm).")

    total_elapsed = time.perf_counter() - t_start

    # Summary
    sep = "=" * 50
    print(f"\n{sep}")
    print("BUILD SUMMARY")
    print(sep)
    print(f"  Total time:  {total_elapsed:.1f}s")
    print(f"  Succeeded:   {len(all_succeeded)}/{len(all_succeeded) + len(all_failed)}")
    if all_succeeded:
        print("  Scripts OK:")
        for name in all_succeeded:
            print(f"    ✓ {name}")
    if all_failed:
        print("  Scripts FAILED:")
        for name in all_failed:
            print(f"    ✗ {name}")
    print(sep)

    return 1 if all_failed else 0


if __name__ == "__main__":
    raise SystemExit(main())
