#!/usr/bin/env python3
"""
Estrae tutte le immagini embedded nei file DVR (xlsx + docx) e le salva in
`storage/agenzie/<codice_fisico>/`. Per ogni immagine inserisce un record in
`agenzie_allegati` (tipo='altro' per ora, ordine = ordine di apparizione nel file).

Uso:
    python3 app/scripts/extract_images.py            # estrae tutte le 800 sedi
    python3 app/scripts/extract_images.py --agenzia 22399_ _   # solo una sede
    python3 app/scripts/extract_images.py --reset    # cancella prima la cartella e gli allegati esistenti

Sorgente file: -mat/DVR/<filename> (preso da dvr.source_file). Se il file è
corrotto a livello di immagine singola, salta quell'immagine (vedi TORRE ANNUNZ.).
"""
from __future__ import annotations
import argparse
import mimetypes
import re
import shutil
import sqlite3
import sys
import warnings
import zipfile
from pathlib import Path

warnings.filterwarnings("ignore")

ROOT = Path(__file__).resolve().parents[2]
import os as _os
DB = Path(_os.environ["IMPORT_DB"]) if _os.environ.get("IMPORT_DB") else ROOT / "import-tools" / "db" / "dev.db"
DVR_DIR = ROOT / "-mat" / "DVR"
STORAGE = ROOT / "storage" / "agenzie"

EXT_OK = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".emf", ".wmf", ".tiff", ".tif"}


def safe_filename(name: str) -> str:
    """Sostituisce caratteri non sicuri per filesystem (mantiene i nomi originali tipo image1.jpeg)."""
    return re.sub(r"[^\w.\-]", "_", name)


def extract_one(conn: sqlite3.Connection, dvr_id: int, agenzia_id: int,
                codice_fisico: str, source_file: str, source_format: str) -> dict:
    cur = conn.cursor()
    safe_cf = safe_filename(codice_fisico)
    sede_dir = STORAGE / safe_cf
    sede_dir.mkdir(parents=True, exist_ok=True)

    src_path = DVR_DIR / source_file
    if not src_path.exists():
        return {"stato": "file_mancante", "estratte": 0, "saltate": 0}

    media_prefix = "xl/media/" if source_format == "xlsx" else "word/media/"

    estratte = saltate = 0
    saltate_files: list[str] = []
    try:
        with zipfile.ZipFile(src_path) as z:
            media_infos = sorted(
                [i for i in z.infolist() if i.filename.startswith(media_prefix) and not i.is_dir()],
                key=lambda i: i.filename,
            )
            for ordine, info in enumerate(media_infos, start=1):
                ext = Path(info.filename).suffix.lower()
                if ext not in EXT_OK:
                    saltate += 1
                    saltate_files.append(f"{info.filename} (ext non valida)")
                    continue
                try:
                    data = z.read(info.filename)
                except Exception as e:
                    saltate += 1
                    saltate_files.append(f"{info.filename} ({type(e).__name__})")
                    continue

                base = Path(info.filename).name  # es. "image1.jpeg"
                out_name = f"{ordine:02d}-{safe_filename(base)}"
                out_path = sede_dir / out_name
                out_path.write_bytes(data)
                mime, _ = mimetypes.guess_type(str(out_path))

                rel_path = str(out_path.relative_to(ROOT))
                cur.execute(
                    """INSERT INTO agenzie_allegati(agenzia_id, tipo, filename, mime_type,
                                                     dimensione_byte, storage_path, note)
                       VALUES (?, 'altro', ?, ?, ?, ?, ?)""",
                    (agenzia_id, base, mime, info.file_size, rel_path,
                     f"da {source_file} (ordine {ordine}/{len(media_infos)})"),
                )
                estratte += 1
    except zipfile.BadZipFile as e:
        return {"stato": "zip_corrotto", "estratte": estratte, "saltate": saltate, "err": str(e)}

    return {"stato": "ok", "estratte": estratte, "saltate": saltate,
            "saltate_files": saltate_files}


def main():
    p = argparse.ArgumentParser()
    p.add_argument("--agenzia", help="codice_fisico per estrarre solo una sede")
    p.add_argument("--reset", action="store_true",
                   help="prima cancella cartella storage e tabella agenzie_allegati")
    args = p.parse_args()

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

    if args.reset and not args.agenzia:
        if STORAGE.exists():
            print(f"Cancello {STORAGE} ...")
            shutil.rmtree(STORAGE)
        cur.execute("DELETE FROM agenzie_allegati")
        conn.commit()

    STORAGE.mkdir(parents=True, exist_ok=True)

    where = ""
    params: list = []
    if args.agenzia:
        where = "WHERE a.codice_fisico = ?"
        params.append(args.agenzia)

    rows = cur.execute(f"""
        SELECT d.id, a.id, a.codice_fisico, d.source_file, d.source_format
        FROM dvr d JOIN agenzie a ON a.id = d.agenzia_id
        {where}
        ORDER BY a.codice_fisico
    """, params).fetchall()

    print(f"Sedi da elaborare: {len(rows)}")
    print(f"Storage:           {STORAGE}")
    print()

    tot_estratte = tot_saltate = tot_zip_ko = tot_file_mancante = 0
    saltate_log: list[tuple[str, list[str]]] = []
    for i, (dvr_id, ag_id, cf, source_file, source_format) in enumerate(rows, 1):
        res = extract_one(conn, dvr_id, ag_id, cf, source_file, source_format)
        if res["stato"] == "ok":
            tot_estratte += res["estratte"]
            tot_saltate += res["saltate"]
            if res["saltate"]:
                saltate_log.append((cf, res["saltate_files"]))
        elif res["stato"] == "zip_corrotto":
            tot_zip_ko += 1
            print(f"  ❌ ZIP corrotto: {cf} ({source_file})")
        elif res["stato"] == "file_mancante":
            tot_file_mancante += 1
            print(f"  ⚠ file mancante: {cf} ({source_file})")
        if i % 100 == 0:
            conn.commit()
            print(f"  ... {i}/{len(rows)} sedi  estratte={tot_estratte:,}")

    conn.commit()
    print()
    print("=" * 70)
    print(f"FINE. Sedi processate: {len(rows)}")
    print(f"Immagini estratte:     {tot_estratte:,}")
    print(f"Immagini saltate:      {tot_saltate}")
    print(f"ZIP corrotti:          {tot_zip_ko}")
    print(f"File mancanti:         {tot_file_mancante}")

    if saltate_log:
        print()
        print(f"Immagini saltate nei {len(saltate_log)} file:")
        for cf, files in saltate_log[:20]:
            print(f"  {cf}: {files}")

    n_alleg = cur.execute("SELECT COUNT(*) FROM agenzie_allegati").fetchone()[0]
    print()
    print(f"Totale record in agenzie_allegati: {n_alleg:,}")
    conn.close()


if __name__ == "__main__":
    main()
