# /// script
# requires-python = ">=3.8"
# dependencies = ["pytest", "pytest-cov"]
# ///
"""
Run pytest with branch coverage for a Python module and check >=90% threshold.

Usage:
  python scripts/run_coverage.py <module_path> <test_file> [--threshold 90]
  python scripts/run_coverage.py --help

Exit codes:
  0 — tests passed AND coverage threshold met
  1 — tests failed OR coverage below threshold
  2 — usage / configuration error
"""

import json
import subprocess
import sys
import argparse
import xml.etree.ElementTree as ET
from pathlib import Path


def _parse_xml(xml_path: Path, module_stem: str, threshold: int) -> dict:
    try:
        root = ET.parse(xml_path).getroot()
    except ET.ParseError as e:
        return {"error": f"Cannot parse coverage.xml: {e}"}

    files, target_data = [], None
    for cls in root.iter("class"):
        fname       = cls.get("filename", "")
        line_rate   = round(float(cls.get("line-rate",   0)) * 100, 1)
        branch_rate = round(float(cls.get("branch-rate", 0)) * 100, 1)
        uncovered   = sorted(int(ln.get("number", 0)) for ln in cls.iter("line") if int(ln.get("hits", 0)) == 0)
        f = {"filename": fname, "line_rate_pct": line_rate, "branch_rate_pct": branch_rate,
             "uncovered_lines": uncovered, "threshold_met": line_rate >= threshold}
        files.append(f)
        if module_stem in fname:
            target_data = f

    if target_data is None and files:
        target_data = files[0]

    overall_line   = round(float(root.get("line-rate",   0)) * 100, 1)
    overall_branch = round(float(root.get("branch-rate", 0)) * 100, 1)
    return {
        "line_rate_pct":   target_data["line_rate_pct"]   if target_data else overall_line,
        "branch_rate_pct": target_data["branch_rate_pct"] if target_data else overall_branch,
        "threshold_met":   target_data["threshold_met"]   if target_data else overall_line >= threshold,
        "uncovered_lines": target_data["uncovered_lines"] if target_data else [],
        "files": files,
    }


def _recommend(cov: dict, threshold: int) -> str:
    pct = cov["line_rate_pct"]
    if pct >= threshold:
        return f"✓ Line coverage {pct}% meets the {threshold}% threshold. Branch: {cov['branch_rate_pct']}%. Done!"
    lines = cov["uncovered_lines"][:15]
    hint  = f" Focus on lines: {lines}." if lines else ""
    return f"✗ Line coverage {pct}% is {round(threshold - pct, 1)}% below threshold.{hint}"


def run(module_path: str, test_file: str, threshold: int) -> dict:
    module = Path(module_path)
    tests  = Path(test_file)
    if not module.exists():
        print(f"Error: Module not found: {module_path}", file=sys.stderr); sys.exit(2)
    if not tests.exists():
        print(f"Error: Test file not found: {test_file}", file=sys.stderr); sys.exit(2)

    cov_xml = Path("coverage.xml")
    cmd = [sys.executable, "-m", "pytest", str(tests),
           f"--cov={module_path}", "--cov-report=xml:coverage.xml",
           "--cov-report=term-missing:skip-covered", "--cov-branch",
           "-v", "--tb=short", "--no-header", f"--cov-fail-under={threshold}"]

    print(f"Running: {' '.join(cmd)}", file=sys.stderr)
    proc = subprocess.run(cmd, capture_output=True, text=True)

    result = {"command": " ".join(cmd), "returncode": proc.returncode,
              "threshold": threshold, "passed": proc.returncode == 0,
              "failed_tests": [], "coverage": None, "recommendation": ""}

    for line in proc.stdout.splitlines():
        if line.startswith("FAILED"):
            result["failed_tests"].append(line.strip())

    if cov_xml.exists():
        result["coverage"] = _parse_xml(cov_xml, module.stem, threshold)
        cov_xml.unlink(missing_ok=True)

    print(proc.stdout, file=sys.stderr)
    if proc.stderr:
        print(proc.stderr, file=sys.stderr)
    if result["coverage"]:
        result["recommendation"] = _recommend(result["coverage"], threshold)
    return result


def main():
    p = argparse.ArgumentParser(
        description="Run pytest with coverage and check the >=90% threshold.",
        epilog="Examples:\n  python scripts/run_coverage.py src/calc.py tests/test_calc.py\n  python scripts/run_coverage.py src/calc.py tests/test_calc.py --threshold 95",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    p.add_argument("module",    help="Path to the Python module under test")
    p.add_argument("test_file", help="Path to the pytest test file")
    p.add_argument("--threshold", type=int, default=90, help="Min. coverage %% required (default: 90)")
    p.add_argument("--format", choices=["json", "text"], default="json")
    args = p.parse_args()

    result = run(args.module, args.test_file, args.threshold)
    if args.format == "json":
        print(json.dumps(result, indent=2))
    else:
        cov = result.get("coverage")
        if cov:
            ok = "✓ PASSED" if cov["threshold_met"] else "✗ FAILED"
            print(f"Line coverage  : {cov['line_rate_pct']}%")
            print(f"Branch coverage: {cov['branch_rate_pct']}%")
            print(f"Threshold ({args.threshold}%): {ok}")
            if cov["uncovered_lines"]:
                print(f"Uncovered lines: {cov['uncovered_lines']}")
        if result["failed_tests"]:
            print("\nFailed tests:")
            for t in result["failed_tests"]: print(f"  {t}")
        if result["recommendation"]:
            print(f"\n{result['recommendation']}")
    sys.exit(0 if result["passed"] else 1)

if __name__ == "__main__":
    main()
