#!/usr/bin/env python3
"""
Per i file in import_log con stato 'no_match', apre il file (xlsx/docx) e legge
la sezione "Dati identificativi" per estrarre indirizzo + città. Poi cerca un
match nell'anagrafica per:
  1. città esatta + numero civico esatto
  2. città esatta + nome via prefix
  3. città esatta (più sedi candidate → manual_review)

Genera report dei tre esiti:
- MATCH_SICURO  → ricarica il file via import_dvr.import_one e aggiorna import_log
- CANDIDATO     → mostra match suggerito e lascia 'no_match' con annotazione
- NESSUN MATCH  → mostra "fuori anagrafica" o serve revisione umana

Esegui: python3 app/scripts/rematch_no_match.py
       python3 app/scripts/rematch_no_match.py --apply   # applica i MATCH_SICURI al DB
"""
from __future__ import annotations
import argparse
import re
import sqlite3
import sys
import warnings
from pathlib import Path

warnings.filterwarnings("ignore")

try:
    from openpyxl import load_workbook
    from docx import Document
except ImportError:
    sys.exit("openpyxl + python-docx richiesti.")

ROOT = Path(__file__).resolve().parents[2]
DB = ROOT / "import-tools" / "db" / "dev.db"
DVR_DIR = ROOT / "-mat" / "DVR"
sys.path.insert(0, str(ROOT / "app" / "scripts"))
from import_dvr import import_one  # noqa: E402


def norm(s: str | None) -> str | None:
    if s is None:
        return None
    out = re.sub(r"\s+", " ", str(s).replace("\xa0", " ").strip())
    return out or None


PROVINCE_IT = {
    "AG","AL","AN","AO","AR","AP","AT","AV","BA","BT","BL","BN","BG","BI","BO","BZ","BS","BR",
    "CA","CL","CB","CI","CE","CT","CZ","CH","CO","CS","CR","KR","CN","EN","FM","FE","FI","FG",
    "FC","FR","GE","GO","GR","IM","IS","SP","AQ","LT","LE","LC","LI","LO","LU","MC","MN","MS",
    "MT","ME","MI","MO","MB","NA","NO","NU","OG","OT","OR","PD","PA","PR","PV","PG","PU","PE",
    "PC","PI","PT","PN","PZ","PO","RG","RA","RC","RE","RI","RN","RM","RO","SA","SS","SV","SI",
    "SR","SO","TA","TE","TR","TO","TP","TN","TV","TS","UD","VA","VE","VB","VC","VR","VV","VI","VT",
}


def norm_city(s: str | None) -> str:
    if not s:
        return ""
    s = re.sub(r"\([^)]*\)", "", s)
    s = s.replace("'", " ").replace("-", " ").replace(".", " ")
    s = re.sub(r"\s+", " ", s.strip().upper())
    parts = s.split()
    while parts and parts[-1] in PROVINCE_IT:
        parts.pop()
    return " ".join(parts)


def city_similarity(a: str, b: str) -> float:
    """0..1 — match stretto: uguali, prefix di 5+ chars, o fuzzy difflib."""
    if not a or not b:
        return 0.0
    if a == b:
        return 1.0
    if len(a) >= 5 and len(b) >= 5:
        if a.startswith(b) or b.startswith(a):
            return 0.95
        if (b in a) or (a in b):
            return 0.9
    import difflib
    return difflib.SequenceMatcher(None, a, b).ratio()


def parse_indirizzo(addr: str) -> tuple[str, str]:
    if not addr:
        return ("", "")
    addr_norm = re.sub(r"\s+", " ", addr.replace(",", " ").strip().upper())
    addr_norm = addr_norm.replace("'", " ").replace(".", " ")
    addr_norm = re.sub(r"\s+", " ", addr_norm).strip()
    m = re.match(r"^(.*?)\s+(\d+\s*[/\-]?\s*[A-Z]?(?:\s+[A-Z])?)$", addr_norm)
    if m:
        return (m.group(1).strip(), re.sub(r"\s+", "", m.group(2)))
    return (addr_norm, "")


def extract_dati_xlsx(path: Path) -> dict:
    wb = load_workbook(path, data_only=True, read_only=True)
    ws = wb["Dati"] if "Dati" in wb.sheetnames else None
    out = {"sede": None, "indirizzo": None, "piano": None, "citta": None}
    if ws is None:
        return out
    for r in range(15, 35):
        d = norm(ws.cell(r, 4).value)
        f = norm(ws.cell(r, 6).value)
        if d and d.lower().startswith("sede:"):
            out["sede"] = d
        if d and f:
            dl = d.lower()
            if dl == "indirizzo":
                out["indirizzo"] = f
            elif dl == "piano":
                out["piano"] = f
            elif dl == "città":
                out["citta"] = f
    return out


def extract_dati_docx(path: Path) -> dict:
    """Riconosce 2 varianti del layout della tabella Dati nei docx:
       Variante A: cells = ["", "indirizzo", "Via X"]  (label in pos 1)
       Variante B: cells = ["indirizzo", "Via X", "Via X"]  (label in pos 0)
    """
    doc = Document(path)
    out = {"sede": None, "indirizzo": None, "piano": None, "citta": None}
    LABELS = {"indirizzo", "piano", "città", "datore di lavoro", "rspp"}
    for table in doc.tables:
        first = (table.rows[0].cells[0].text or "").strip() if table.rows else ""
        if not first.startswith("SEDE:"):
            continue
        out["sede"] = norm(first)
        for row in table.rows:
            cells = [norm(c.text) for c in row.cells]
            if not cells:
                continue
            label = value = None
            for i in range(min(2, len(cells))):
                if cells[i] and cells[i].lower() in LABELS:
                    label = cells[i].lower()
                    if i + 1 < len(cells) and cells[i + 1] and cells[i + 1].lower() not in LABELS:
                        value = cells[i + 1]
                    break
            if not label or not value:
                continue
            if label == "indirizzo":
                out["indirizzo"] = value
            elif label == "piano":
                out["piano"] = value
            elif label == "città":
                out["citta"] = value
        break
    return out


def extract_dati(path: Path) -> dict:
    if path.suffix.lower() == ".xlsx":
        return extract_dati_xlsx(path)
    return extract_dati_docx(path)


def find_match_smart(cur, dati: dict) -> list:
    """Cerca match in anagrafica usando città+indirizzo del file.

    Score:
      - match città esatto         +1
      - match città fuzzy (>=0.85) +0 (con flag fuzzy: penalizzazione successiva)
      - civico esatto              +2
      - parole comuni della via    +N (max 4)
      - bonus provincia            +1 se entrambe presenti e uguali
      - penalty provincia          -1 se entrambe presenti e diverse (ma non escludo)
    """
    citta = dati.get("citta") or ""
    citta_norm = norm_city(citta)
    if not citta_norm:
        return []
    prov_m = re.search(r"\(([A-Z]{2})\)", citta)
    prov = prov_m.group(1) if prov_m else None

    via_norm, civico = parse_indirizzo(dati.get("indirizzo") or "")

    rows = list(cur.execute("""
        SELECT id, codice_fisico, tipologia, nome_agenzia, nome_sede, indirizzo, citta, provincia
        FROM agenzie
    """))

    candidates = []
    for ag_id, cod_fis, tipo, ag, sede, addr, c_db, prov_db in rows:
        c_db_norm = norm_city(c_db)
        sim = city_similarity(citta_norm, c_db_norm)
        if sim < 0.85:
            continue

        addr_db_via, addr_db_civ = parse_indirizzo(addr)
        score = 1.0 if sim >= 0.99 else sim
        if civico and addr_db_civ and civico == addr_db_civ:
            score += 2
        if via_norm and addr_db_via:
            via_words = via_norm.split()
            db_words = addr_db_via.split()
            common = sum(1 for w in via_words if len(w) >= 3 and w in db_words)
            score += min(common, 4)
        if prov and prov_db:
            if prov == prov_db:
                score += 1
            else:
                score -= 1
        candidates.append({
            "ag_id": ag_id, "cod_fis": cod_fis, "tipo": tipo,
            "ag": ag, "sede": sede, "addr": addr, "citta": c_db, "prov": prov_db,
            "score": score, "city_sim": sim,
        })
    candidates.sort(key=lambda x: -x["score"])
    return candidates


def main():
    p = argparse.ArgumentParser()
    p.add_argument("--apply", action="store_true",
                   help="Applica i match SICURI al DB (ricarica i file)")
    args = p.parse_args()

    conn = sqlite3.connect(DB)
    conn.execute("PRAGMA foreign_keys = ON")
    cur = conn.cursor()

    no_match = cur.execute(
        "SELECT id, filename FROM import_log WHERE stato = 'no_match' ORDER BY filename"
    ).fetchall()
    print(f"File no_match da rianalizzare: {len(no_match)}")
    print()

    sicuri = []     # match score >= 4 e singolo top-1
    candidati = []  # match score 1-3 o multipli
    nessuno = []    # nessun candidato

    for log_id, fn in no_match:
        path = DVR_DIR / fn
        if not path.exists():
            nessuno.append((log_id, fn, "file mancante", []))
            continue
        try:
            dati = extract_dati(path)
        except Exception as e:
            nessuno.append((log_id, fn, f"extract_error: {e}", []))
            continue

        candidates = find_match_smart(cur, dati)
        if not candidates:
            nessuno.append((log_id, fn, "no candidate per città", []))
            continue

        top = candidates[0]
        same_score = [c for c in candidates if c["score"] == top["score"]]
        if top["score"] >= 4 and len(same_score) == 1:
            sicuri.append((log_id, fn, dati, top, candidates))
        else:
            candidati.append((log_id, fn, dati, top, candidates))

    print("=" * 78)
    print(f"MATCH SICURO (score ≥ 4, top-1 univoco): {len(sicuri)}")
    print(f"CANDIDATO (da rivedere o multi-match):   {len(candidati)}")
    print(f"NESSUN MATCH (fuori anagrafica):         {len(nessuno)}")
    print()

    print("=" * 78)
    print("=== MATCH SICURI ===")
    for log_id, fn, dati, top, _ in sicuri:
        print(f"  ✓ {fn[:70]}")
        print(f"      file dice:  città={dati.get('citta')!r:<35} via={dati.get('indirizzo')!r}")
        print(f"      anagr:      {top['cod_fis']:<10} {top['ag']} / {top['sede']}")
        print(f"                  {top['addr']} - {top['citta']} ({top['prov']})  score={top['score']}")

    print()
    print("=" * 78)
    print(f"=== CANDIDATI (top-N {min(3, len(candidati))} per fn, rivedere a mano) ===")
    for log_id, fn, dati, top, all_c in candidati[:30]:
        print(f"  ? {fn[:70]}")
        print(f"      file dice:  città={dati.get('citta')!r:<35} via={dati.get('indirizzo')!r}")
        for c in all_c[:2]:
            print(f"      candidato:  [{c['score']}] {c['cod_fis']:<10} {c['ag']} / {c['sede']}  -- {c['addr']}, {c['citta']}")

    print()
    print("=" * 78)
    print("=== NESSUN MATCH ===")
    for log_id, fn, motivo, _ in nessuno:
        print(f"  ✗ {fn[:70]}  [{motivo}]")

    if args.apply and sicuri:
        print()
        print("=" * 78)
        print(f"APPLY: registro override per {len(sicuri)} file con match sicuro...")
        for log_id, fn, dati, top, _ in sicuri:
            cur.execute(
                """INSERT OR REPLACE INTO filename_overrides
                   (filename, agenzia_id, match_score, reason)
                   VALUES (?, ?, ?, ?)""",
                (fn, top["ag_id"], top["score"],
                 f"smart match (city+address): file dice '{dati.get('citta')}' / '{dati.get('indirizzo')}' "
                 f"→ {top['cod_fis']} {top['ag']}/{top['sede']}"),
            )
            cur.execute("DELETE FROM import_log WHERE id = ?", (log_id,))
        conn.commit()

        print(f"  Override registrati: {len(sicuri)}")
        print()
        print(f"Ora ricarico i {len(sicuri)} file via import_one()...")
        ok = ko = 0
        for log_id, fn, dati, top, _ in sicuri:
            path = DVR_DIR / fn
            try:
                r = import_one(conn, path)
                if r["stato"] == "ok":
                    ok += 1
                else:
                    ko += 1
                    print(f"    KO {fn}: stato={r['stato']}")
            except Exception as e:
                ko += 1
                print(f"    EXC {fn}: {type(e).__name__}: {e}")
        conn.commit()
        print(f"  Risultato: ok={ok}, ko={ko}")

    conn.close()


if __name__ == "__main__":
    main()
