"""
Test/démo du solveur : charge sample_request.json, résout, et imprime un
horaire lisible + le rapport de couverture (dispo vs requis). Sert aussi de
vérification (assert : statut faisable + chaque tech respecte ses contraintes).

    python test_solver.py
"""
import json
import os
import sys
from collections import defaultdict
from solver import solve_roster

HERE = os.path.dirname(os.path.abspath(__file__))


def main():
    with open(os.path.join(HERE, "sample_request.json")) as f:
        req = json.load(f)

    res = solve_roster(req)

    print(f"\n=== STATUT : {res['status']}  ({res.get('solve_ms')} ms) ===")
    assert res["status"] in ("OPTIMAL", "FEASIBLE"), "solveur infaisable"

    # Grille employé × jour
    days = [req["horizon"]["start"]]
    from datetime import date, timedelta
    y, m, d = (int(x) for x in req["horizon"]["start"].split("-"))
    start = date(y, m, d)
    days = [(start + timedelta(days=i)).isoformat() for i in range(req["horizon"]["days"])]

    by_tech = defaultdict(dict)
    for a in res["assignments"]:
        by_tech[a["tech_name"]][a["date"]] = f"{a['shift']}@{a['zone']}"

    print("\n=== HORAIRE (Roster AI) ===")
    hdr = "Technicien".ljust(16) + "".join(dd[5:].ljust(12) for dd in days)
    print(hdr)
    for t in req["technicians"]:
        row = t["name"].ljust(16)
        for dd in days:
            row += (by_tech.get(t["name"], {}).get(dd, "·")).ljust(12)
        print(row)

    print("\n=== HEURES / TECH (équité, écart =", res.get("spread_hours"), "h) ===")
    for tid, h in sorted(res["tech_hours"].items(), key=lambda kv: -kv[1]):
        name = next((t["name"] for t in req["technicians"] if t["id"] == tid), tid)
        print(f"  {name.ljust(16)} {h} h")

    print("\n=== COUVERTURE (dispo vs requis) ===")
    for c in res["coverage_report"]:
        flag = "  ✅" if c["shortfall"] == 0 else f"  ❌ MANQUE {c['shortfall']}"
        print(f"  {c['date']}  {c['shift']:5} {c['zone']:10} requis={c['required']} assigné={c['assigned']}{flag}")

    print("\n=== EXPLICATIONS ===")
    for e in res["explanations"]:
        print("  " + e)

    # Vérifs dures
    # 1) ≤ 1 shift/jour/tech
    seen = set()
    for a in res["assignments"]:
        key = (a["tech"], a["date"])
        assert key not in seen, f"double-booking {key}"
        seen.add(key)
    # 2) compétences respectées
    skill_of = {t["id"]: set(t["skills"]) for t in req["technicians"]}
    cov_skills = {(c["date"], c["shift"], c.get("zone", "—")): set(c.get("required_skills", []))
                  for c in req["coverage"]}
    for a in res["assignments"]:
        req_sk = cov_skills.get((a["date"], a["shift"], a["zone"]), set())
        assert req_sk.issubset(skill_of[a["tech"]]), f"compétence manquante: {a}"
    # 3) indispo respectée
    unavail = {t["id"]: set(t.get("unavailable", [])) for t in req["technicians"]}
    for a in res["assignments"]:
        assert a["date"] not in unavail[a["tech"]], f"assigné un jour indispo: {a}"

    print("\n✅ Toutes les contraintes dures respectées (1 shift/jour, compétences, indispos).")
    return 0


if __name__ == "__main__":
    sys.exit(main())
