#!/usr/bin/env python3
"""Export public MIR, reproductibil, fără dependențe sau acces la rețea."""
import argparse
from collections import Counter
import csv
from datetime import date
import hashlib
import io
import json
from pathlib import Path
import re
import sys

HERE = Path(__file__).resolve().parent
DEFAULT_ASSETS = HERE.parent
FIELDS = ["id", "nume", "judet", "lot", "data_solicitarii", "categorie_raspuns", "data_raspuns", "url_dosar"]
CATEGORIES = {
    "SUBSTANTIVE_RESPONSE": ("substantive_response_institutions", 36),
    "REFUSAL_WITH_PARTIAL_FACTUAL_RESPONSE": ("refusal_with_partial_factual_response_institutions", 3),
    "OTHER_HUMAN_OR_PROCEDURAL_REACTION": ("other_human_or_procedural_reaction_institutions", 8),
    "REGISTRATION_ONLY": ("registration_only_institutions", 10),
    "AUTOMATIC_ONLY": ("automatic_only_institutions", 3),
    "NO_RESPONSE_OBSERVED": ("no_substantive_response_observed_institutions", 268),
    "AWAITING_RESPONSE": ("awaiting_response_institutions", 40),
}
# Selecție editorială din auditul metodologia publică, nu clasificare automată a prozei.
ABSENCE_AGGREGATE = ["001", "046", "052", "062", "186", "198", "218", "295", "355"]
ABSENCE_NARROW = ["103", "162", "316"]


def require(condition, message):
    if not condition:
        raise ValueError(message)


def iso(value):
    require(isinstance(value, str) and re.fullmatch(r"\d{4}-\d{2}-\d{2}", value), "Dată ISO invalidă")
    date.fromisoformat(value)
    return value


def safe_text(value):
    require(isinstance(value, str) and bool(value.strip()), "Câmp textual obligatoriu gol")
    require(not any(ord(c) < 32 for c in value), "Caracter de control în export")
    require(not value.lstrip().startswith(("=", "+", "-", "@")), "Celulă potențial executabilă în spreadsheet")
    require("@" not in value and "mailto:" not in value.lower(), "Adresă email în export")
    return value


def build(registry, stats, comparison, assets):
    records = registry["records"]
    ids = [r["public_id"] for r in records]
    require(len(records) == 372 and len(set(ids)) == 372, "Sunt necesare 372 ID-uri sursă distincte")
    require(all(re.fullmatch(r"MIR-SRC-\d{3}", x) for x in ids), "ID sursă invalid")
    excluded = {r["public_id"]: r["outreach"]["state"] for r in records if r["outreach"]["state"] != "Transmis"}
    require(set(excluded) == {"MIR-SRC-119", "MIR-SRC-289", "MIR-SRC-326", "MIR-SRC-328"}, "Excluderile s-au schimbat; revizuiește metodologia")
    for r in records:
        if r["public_id"] in excluded:
            require(not r["outreach"]["sent_dates"], "Fișă exclusă cu expediere consemnată")
    rows = []
    for r in sorted(records, key=lambda r: r["public_id"]):
        if r["public_id"] in excluded:
            continue
        dates = [iso(d) for d in r["outreach"]["sent_dates"]]
        require(bool(dates), "Instituție contactată fără dată de solicitare")
        sent = min(dates)
        require("2026-08-28" <= sent <= "2026-09-28", "Data nu aparține ediției documentate")
        category = r["response"]["category_public"]
        require(category in CATEGORIES, "Categorie nouă: actualizează dicționarul")
        response_date = r["response"].get("date")
        if response_date:
            iso(response_date)
            require(sent <= response_date <= registry["as_of"][:10], "Cronologie imposibilă")
        require(category not in {"NO_RESPONSE_OBSERVED", "AWAITING_RESPONSE"} or not response_date, "Lipsă de reacție cu dată de răspuns")
        slug = r["public_id"].lower()
        require((assets.parent / "institutii" / (slug + ".html")).is_file(), f"Dosar local lipsă: {slug}")
        rows.append(dict(zip(FIELDS, [
            r["public_id"], safe_text(r["institution"]), safe_text(r["location"]["county"]),
            "lot_2_20260928" if sent == "2026-09-28" else "lot_1_20260828_20260906",
            sent, category, response_date or "", "https://medicalintegrityromania.org/institutii/" + slug,
        ])))
    require(len(rows) == 368, "Numărul instituțiilor contactate diferă de 368")
    require(len({r['nume'].casefold() for r in rows}) == 368, "Nume duplicate: verificare instituțională necesară")
    counts = Counter(r["categorie_raspuns"] for r in rows)
    cohorts = Counter(r["lot"] for r in rows)
    require(cohorts == {"lot_1_20260828_20260906": 312, "lot_2_20260928": 56}, "Loturile nu sunt 312 + 56")
    require(stats["denominator"] == registry["counts"]["contacted_legal_institutions"] == len(rows), "Numitor JSON neconcordant")
    require(stats["institution_rows"] == registry["counts"]["institution_source_rows"] == len(records), "Număr fișe neconcordant")
    require(stats["cutoff"] == registry["as_of"] == "2026-10-15T10:55:53+03:00", "Snapshot schimbat; verifică pagina și metodologia")
    stat_categories = {c["key"]: c["count"] for c in stats["categories"]}
    require(len(stat_categories) == len(stats["categories"]) == len(CATEGORIES), "Categorii statistice duplicate sau suplimentare")
    for category, (key, expected) in CATEGORIES.items():
        require(counts[category] == expected == registry["counts"][key] == stat_categories[key], f"Total neconcordant: {category}")
    require(all(r["lot"].startswith("lot_1") for r in rows if r["categorie_raspuns"] == "NO_RESPONSE_OBSERVED"), "Lot greșit pentru lipsa răspunsului")
    require(all(r["lot"].startswith("lot_2") for r in rows if r["categorie_raspuns"] == "AWAITING_RESPONSE"), "Lot greșit pentru așteptare")
    new = [r for r in rows if r["lot"].startswith("lot_2")]
    pending = sum(r["categorie_raspuns"] != "SUBSTANTIVE_RESPONSE" for r in new)
    require(pending == stats["new_cohort_awaiting_substantive_response"] == registry["counts"]["new_cohort_awaiting_substantive_response"] == 50, "Sold lot nou neconcordant")
    require(sum(r["categorie_raspuns"] == "REGISTRATION_ONLY" for r in new) == stats["new_cohort_registration_only"] == registry["counts"]["new_cohort_registration_only"] == 5, "Înregistrări lot nou neconcordante")
    require(stats["new_cohort_without_reaction"] == 40 and stats["previous_cohort_denominator"] == 312, "Metadate loturi neconcordante")
    require(registry["counts"]["legal_due_dates_adjudicated"] == 0, "Revizuiește nota despre termene")
    comparison_ids = [r["ID"] for r in comparison]
    require(len(comparison_ids) == len(set(comparison_ids)) == 27, "Setul comparativ nu are 27 ID-uri distincte")
    by_id = {r["id"]: r for r in rows}
    require(set(comparison_ids) <= set(by_id), "ID comparativ absent din registru")
    comparative_categories = Counter(by_id[x]["categorie_raspuns"] for x in comparison_ids)
    require(comparative_categories == {"SUBSTANTIVE_RESPONSE": 25, "REFUSAL_WITH_PARTIAL_FACTUAL_RESPONSE": 2}, "Setul comparativ nu este 25 fond + 2 refuzuri")
    absence_ids = ["MIR-SRC-" + x for x in ABSENCE_AGGREGATE + ABSENCE_NARROW]
    require(len(set(absence_ids)) == 12 and set(absence_ids) <= set(comparison_ids), "Selecția editorială 9 + 3 nu se reconciliază")
    report = {
        "status": "PASS", "snapshot": registry["as_of"], "institutii_exportate": len(rows),
        "fise_sursa": len(records), "excluderi": excluded, "loturi": dict(cohorts),
        "categorii": dict(counts), "fara_reactie_observata": 308,
        "date_raspuns_lipsa_la_categorii_cu_reactie": sum(not r["data_raspuns"] for r in rows if r["categorie_raspuns"] not in {"NO_RESPONSE_OBSERVED", "AWAITING_RESPONSE"}),
        "set_comparativ": {"institutii": 27, "fond": 25, "refuzuri": 2, "absenta_agregat": ABSENCE_AGGREGATE, "absenta_document_mai_restrans": ABSENCE_NARROW,
                          "metoda": "Selecție editorială din audit; scriptul verifică apartenența și totalurile, nu interpretează declarațiile."},
    }
    buffer = io.StringIO(newline="")
    writer = csv.DictWriter(buffer, fieldnames=FIELDS, lineterminator="\n")
    writer.writeheader()
    writer.writerows(rows)
    return buffer.getvalue().encode("utf-8"), report


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--assets", type=Path, default=DEFAULT_ASSETS, help="Directorul surselor, numai citire")
    parser.add_argument("--check", action="store_true", help="Verifică exportul existent fără să îl rescrie")
    args = parser.parse_args()
    assets = args.assets.resolve()
    names = ["registru-mir-20260913.json", "mir-campaign-stats.json", "comparatie-raspunsuri-mir-20260917.csv"]
    raw = {name: (assets / name).read_bytes() for name in names}
    registry = json.loads(raw[names[0]].decode("utf-8-sig"))
    stats = json.loads(raw[names[1]].decode("utf-8-sig"))
    comparison = list(csv.DictReader(io.StringIO(raw[names[2]].decode("utf-8-sig"))))
    output, report = build(registry, stats, comparison, assets)
    report["sha256_surse"] = {name: hashlib.sha256(value).hexdigest() for name, value in raw.items()}
    report["sha256_csv"] = hashlib.sha256(output).hexdigest()
    target = HERE / "pontaj-real-institutii.csv"
    evidence = HERE / "VERIFICARE_DATE.json"
    # Verifică și modificarea concurentă a celor trei intrări în timpul generării.
    require(all((assets / n).read_bytes() == b for n, b in raw.items()), "Sursele au fost modificate în timpul rulării; reia")
    if args.check:
        require(target.read_bytes() == output, "CSV-ul existent diferă de regenerare")
        require(json.loads(evidence.read_text(encoding="utf-8")) == report, "Dovada existentă diferă; sursele sau exportul s-au schimbat")
    else:
        target.write_bytes(output)
        evidence.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print("PASS — 368 instituții; loturi 312 + 56; 36 fond; 268 + 40 fără reacție; set comparativ 27, selecție editorială 9 + 3.")


if __name__ == "__main__":
    try:
        main()
    except (ValueError, KeyError, TypeError, OSError) as exc:
        print(f"EROARE: {exc}", file=sys.stderr)
        sys.exit(1)
