#!/usr/bin/env python3
"""
Ordina i file di una cartella in sottocartelle per anno e mese.

Esempio della guida di Promptificio "Script Python con Claude".
Usa solo librerie incluse in Python: non serve installare niente.

Come funziona:
- cerca una data nel nome del file (per esempio 20260314 o 2026-03-14);
  se non la trova, usa la data dell'ultima modifica del file;
- sposta ogni file in  <cartella>/ordinati/AAAA/MM/  aggiungendo la data
  davanti al nome (AAAA-MM-GG_nome-originale);
- non cancella mai niente e non sovrascrive: se un nome esiste già aggiunge _2, _3...;
- scrive un registro CSV con ogni spostamento, che serve anche ad annullare.

Uso:
  python3 ordina-file.py CARTELLA              -> prova: mostra cosa farebbe, non tocca niente
  python3 ordina-file.py CARTELLA --esegui     -> sposta davvero i file
  python3 ordina-file.py CARTELLA --annulla REGISTRO.csv  -> rimette i file dov'erano
(su Windows scrivi "py" al posto di "python3")
"""
import argparse
import csv
import datetime as dt
import re
import shutil
import sys
from pathlib import Path

# Date nel nome: 20260314, 2026-03-14, 2026_03_14, 2026.03.14
DATA_NEL_NOME = re.compile(r"(20\d{2})[-_.]?(0[1-9]|1[0-2])[-_.]?(0[1-9]|[12]\d|3[01])")
CARTELLA_DESTINAZIONE = "ordinati"


def data_del_file(percorso: Path):
    """Restituisce (data, origine): dal nome se c'è, altrimenti dalla data di modifica."""
    m = DATA_NEL_NOME.search(percorso.name)
    if m:
        try:
            return dt.date(int(m.group(1)), int(m.group(2)), int(m.group(3))), "nome"
        except ValueError:
            pass  # per esempio 2026-02-31: non è una data vera, uso la data di modifica
    return dt.date.fromtimestamp(percorso.stat().st_mtime), "modifica"


def nome_libero(destinazione: Path, occupati: set) -> Path:
    """Se il nome è già usato (sul disco o in questa esecuzione), aggiunge _2, _3... prima dell'estensione."""
    if not destinazione.exists() and destinazione not in occupati:
        return destinazione
    n = 2
    while True:
        prova = destinazione.with_name(f"{destinazione.stem}_{n}{destinazione.suffix}")
        if not prova.exists() and prova not in occupati:
            return prova
        n += 1


def ordina(cartella: Path, esegui: bool) -> int:
    file = sorted(p for p in cartella.iterdir() if p.is_file() and not p.name.startswith("."))
    if not file:
        print("Nella cartella non ci sono file da ordinare.")
        return 0
    adesso = dt.datetime.now().strftime("%Y%m%d-%H%M%S")
    registro = cartella / f"registro-ordina-file-{adesso}.csv"
    righe = []
    previsti = set()  # in prova i file non vengono spostati: tengo conto dei nomi già "occupati"
    for p in file:
        if p.suffix.lower() == ".csv" and p.name.startswith("registro-ordina-file-"):
            continue  # non sposto i registri delle esecuzioni precedenti
        data, origine = data_del_file(p)
        nuova_cartella = cartella / CARTELLA_DESTINAZIONE / f"{data:%Y}" / f"{data:%m}"
        nuovo_nome = p.name if p.name.startswith(f"{data:%Y-%m-%d}_") else f"{data:%Y-%m-%d}_{p.name}"
        destinazione = nome_libero(nuova_cartella / nuovo_nome, previsti)
        previsti.add(destinazione)
        righe.append({"da": str(p), "a": str(destinazione), "data": data.isoformat(), "data_presa_da": origine})
        fonte = "dal nome" if origine == "nome" else "dalla data di modifica"
        print(f"{'SPOSTO ' if esegui else 'PROVA  '} {p.name}  ->  {destinazione.relative_to(cartella)}  (data presa {fonte})")
        if esegui:
            destinazione.parent.mkdir(parents=True, exist_ok=True)
            shutil.move(str(p), str(destinazione))
    if esegui:
        with registro.open("w", newline="", encoding="utf-8") as f:
            w = csv.DictWriter(f, fieldnames=["da", "a", "data", "data_presa_da"])
            w.writeheader()
            w.writerows(righe)
        print(f"\nFatto: {len(righe)} file spostati. Registro: {registro.name}")
    else:
        print(f"\nProva completata: {len(righe)} file verrebbero spostati. Niente è stato modificato.")
        print("Se va bene, rilancia con --esegui.")
    return len(righe)


def annulla(registro: Path) -> int:
    with registro.open(newline="", encoding="utf-8") as f:
        righe = list(csv.DictReader(f))
    rimessi = 0
    for r in reversed(righe):
        da, a = Path(r["a"]), Path(r["da"])
        if not da.exists():
            print(f"Non trovo {da.name}: lo salto.")
            continue
        if a.exists():
            print(f"Esiste già {a.name} nella posizione originale: lo salto per non sovrascriverlo.")
            continue
        a.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(str(da), str(a))
        rimessi += 1
    print(f"Annullato: {rimessi} file rimessi al loro posto.")
    return rimessi


def main():
    ap = argparse.ArgumentParser(description="Ordina i file in sottocartelle per anno e mese.")
    ap.add_argument("cartella", help="la cartella con i file da ordinare")
    ap.add_argument("--esegui", action="store_true", help="sposta davvero i file (senza: solo prova)")
    ap.add_argument("--annulla", metavar="REGISTRO", help="rimette i file dov'erano usando il registro CSV")
    args = ap.parse_args()
    cartella = Path(args.cartella).expanduser()
    if not cartella.is_dir():
        sys.exit(f"Non trovo la cartella: {cartella}")
    if args.annulla:
        reg = Path(args.annulla)
        if not reg.is_absolute():
            reg = cartella / reg
        if not reg.exists():
            sys.exit(f"Non trovo il registro: {reg}")
        annulla(reg)
    else:
        ordina(cartella, args.esegui)


if __name__ == "__main__":
    main()
