"""Assemblage : plan déterministe (`build`) et matrice live (`run`).

Deux artefacts, deux natures :

  build → PLAN (out/regression_plan.json) : recensement EXHAUSTIF et
          DÉTERMINISTE des suites gated (chemin, jobs, gate, fichiers/méthodes
          test_*). Aucun compteur de résultat → diffable, re-générable, commité.
          Verdict PASS ssi couverture prouvée + chaque suite ≥ planchers.

  run  → MATRICE live : exécute réellement chaque suite et agrège les compteurs
         (ran/passed/failures/errors/skipped) + verdict global. Aucun horodatage /
         hôte / chemin absolu → deux `run` sont byte-identiques : l'artefact
         `regression_run.json` EST commité et byte-gaté par `ci/check_regression.sh`
         (voir .gitignore), c'est la sortie qui fait autorité sur « N tests verts ».

Aucune date/horodatage → build reproductible (gate CI stable)."""

from __future__ import annotations

import os
from typing import Any

from . import discovery, runner
from .deps import DELIVERABLES_ROOT

MATRIX_NAME = "OTO QA · Matrice de régression exhaustive"


def build_plan(spec: dict[str, Any]) -> dict[str, Any]:
    """Plan déterministe des suites gated (sans exécution)."""
    suites = discovery.discover_suites(spec)
    coverage = discovery.coverage_report(spec, suites)
    th = spec["thresholds"]
    min_files = th["min_test_files"]
    min_methods = th["min_methods_per_suite"]

    under = [
        s["path"] for s in suites
        if s["test_files"] < min_files or s["test_methods"] < min_methods
    ]
    totals = {
        "suites": len(suites),
        "test_files": sum(s["test_files"] for s in suites),
        "test_methods": sum(s["test_methods"] for s in suites),
        "min_methods": min((s["test_methods"] for s in suites), default=0),
        "under_threshold": len(under),
    }
    verdict = "PASS" if (coverage["ok"] and not under) else "FAIL"

    return {
        "matrix": MATRIX_NAME,
        "version": spec["version"],
        "reference_cadre": spec["reference_cadre"],
        "self_module": spec["self_module"],
        "thresholds": {
            "min_test_files": min_files,
            "min_methods_per_suite": min_methods,
        },
        "discover_cmd": list(spec["discover_cmd"]),
        "coverage": coverage,
        "suites": suites,
        "under_threshold": under,
        "totals": totals,
        "verdict": verdict,
        "notes": spec.get("notes", []),
    }


def run_matrix(spec: dict[str, Any], *, python: str | None = None) -> dict[str, Any]:
    """Exécute réellement toutes les suites du plan et agrège les compteurs.

    Byte-déterministe (aucun horodatage/hôte/chemin absolu) → l'artefact
    `regression_run.json` EST commité et byte-gaté par `ci/check_regression.sh`."""
    suites = discovery.discover_suites(spec)
    discover_cmd = list(spec["discover_cmd"])
    # `python3 -m unittest ...` → on retire le binaire, on garde les args.
    cmd_args = discover_cmd[1:] if discover_cmd and discover_cmd[0].startswith("python") \
        else discover_cmd

    results: list[dict] = []
    for s in suites:
        abs_path = os.path.join(DELIVERABLES_ROOT, s["path"])
        res = runner.run_suite(abs_path, python=python, discover_cmd=cmd_args)
        results.append({
            "id": s["id"],
            "path": s["path"],
            "expected_methods": s["test_methods"],
            **res,
        })

    totals = {
        "suites": len(results),
        "green": sum(1 for r in results if r["ok"]),
        "red": sum(1 for r in results if not r["ok"]),
        "ran": sum(r["ran"] for r in results),
        "passed": sum(r["passed"] for r in results),
        "failures": sum(r["failures"] for r in results),
        "errors": sum(r["errors"] for r in results),
        "skipped": sum(r["skipped"] for r in results),
    }
    verdict = "PASS" if totals["red"] == 0 and totals["suites"] > 0 else "FAIL"
    return {
        "matrix": MATRIX_NAME,
        "version": spec["version"],
        "self_module": spec["self_module"],
        "suites": results,
        "totals": totals,
        "verdict": verdict,
    }
