"""
Genera link_google_maps.txt e valida i file dell'itinerario.

Controlli eseguiti:
  - CSV leggibili, intestazioni attese, nessun campo mancante
  - coordinate numeriche, dentro un bounding box stretto
  - latitudine e longitudine non invertite (controllo esplicito)
  - KML ben formato, cartelle attese presenti, coordinate valide
  - link Google Maps correttamente codificati e con pochi waypoint

Uso: py valida.py
"""

import csv
import os
import sys
import xml.etree.ElementTree as ET
from urllib.parse import urlencode, quote, urlparse, parse_qs

BASE = os.path.dirname(os.path.abspath(__file__))
CSV_PATH = os.path.join(BASE, "abruzzo_itinerario.csv")
BUDGET_PATH = os.path.join(BASE, "budget_viaggio.csv")
KML_PATH = os.path.join(BASE, "abruzzo_itinerario.kml")
LINKS_PATH = os.path.join(BASE, "link_google_maps.txt")

# Bounding box stretto: Campania nord + Abruzzo. Il limite est e' volutamente
# a 15.0 perche' Nominatim, interrogato con "Salerno, Campania, Italia",
# restituisce il CONFINE PROVINCIALE il cui centroide cade a 15.31 E,
# nell'entroterra cilentano, a ~80 km dalla citta'.
LAT_MIN, LAT_MAX = 40.4, 42.6
LON_MIN, LON_MAX = 13.3, 15.0

MAX_WAYPOINT = 3  # pochi waypoint per link: cosi' funzionano anche da smartphone

GIORNATE = [
    (
        "GIORNO 1 - domenica 9 agosto - Salerno -> Villalago",
        ("Salerno, Piazza della Liberta", "40.6762928,14.7537488"),
        [("Castel di Sangro (sosta pranzo)", "Castel di Sangro AQ"),
         ("Villetta Barrea", "41.8013605,13.9359508")],
        ("Camping I Lupi - Villalago Riviera", "41.9277113,13.8534806"),
    ),
    (
        "GIORNO 1-bis - bagno serale al Lago di Scanno",
        ("Camping I Lupi - Villalago", "41.9277113,13.8534806"),
        [],
        ("Lago di Scanno - spiaggia lato Villalago", "41.9211167,13.8635543"),
    ),
    (
        "GIORNO 2 - lunedi 10 agosto - anello San Domenico / Daini / Barrea",
        ("Camping I Lupi - Villalago", "41.9277113,13.8534806"),
        [("Lago ed Eremo di San Domenico", "41.9427162,13.8290272"),
         ("Centro Visita Daini - Villetta Barrea", "41.8026711,13.9386292"),
         ("Lago di Barrea - punto La Gravara", "41.7634550,13.9648630")],
        ("Camping I Lupi - Villalago", "41.9277113,13.8534806"),
    ),
    (
        "GIORNO 3 - martedi 11 agosto - Villalago -> canoa sul Tirino -> Capestrano",
        ("Camping I Lupi - Villalago", "41.9277113,13.8534806"),
        [("Sulmona (spesa e rifornimento)", "Sulmona AQ"),
         ("Imbarco canoa - Via Capodacqua 7, Capestrano", "42.2867812,13.7896262")],
        ("Camping San Martino - Capestrano", "42.2651013,13.7988788"),
    ),
    (
        "GIORNO 3-bis - dall'imbarco al Lago di Capo d'Acqua (1 km)",
        ("Imbarco canoa - Capestrano", "42.2867812,13.7896262"),
        [],
        ("Lago di Capo d'Acqua", "42.2808460,13.7920056"),
    ),
    (
        "GIORNO 4 - mercoledi 12 agosto - Capestrano -> Gole dell'Alento -> Caramanico",
        ("Camping San Martino - Capestrano", "42.2651013,13.7988788"),
        [("Gole dell'Alento - Serramonacesca", "42.2332425,14.1021469")],
        ("Caramanico Terme (B&B)", "42.1564460,14.0031895"),
    ),
    (
        "GIORNO 5 - giovedi 13 agosto - Caramanico Terme -> Salerno",
        ("Caramanico Terme", "42.1564460,14.0031895"),
        [("Sulmona", "Sulmona AQ"),
         ("Castel di Sangro", "Castel di Sangro AQ"),
         ("Venafro (ingresso A1 Caianello)", "Venafro IS")],
        ("Salerno, Piazza della Liberta", "40.6762928,14.7537488"),
    ),
]


def costruisci_url(origine, waypoints, destinazione):
    params = [("api", "1"), ("origin", origine), ("destination", destinazione)]
    if waypoints:
        params.append(("waypoints", "|".join(waypoints)))
    params.append(("travelmode", "driving"))
    return "https://www.google.com/maps/dir/?" + urlencode(params, quote_via=quote, safe="")


def genera_links():
    r = ["LINK GOOGLE MAPS - ABRUZZO 9-13 AGOSTO 2026",
         "Generati il 2026-08-06. Un link per giornata, massimo %d waypoint each." % MAX_WAYPOINT,
         "Ordine: origine -> waypoint -> destinazione. Tutti gli URL sono URL-encoded.",
         "I km e i tempi nel file .md sono STIME: apri questi link per il dato reale.",
         ""]
    for titolo, orig, wps, dest in GIORNATE:
        if len(wps) > MAX_WAYPOINT:
            raise ValueError("Troppi waypoint: " + titolo)
        r += ["=" * 78, titolo, "-" * 78, "Origine:      " + orig[0]]
        for i, w in enumerate(wps, 1):
            r.append("Waypoint %d:   %s" % (i, w[0]))
        r += ["Destinazione: " + dest[0], "",
              costruisci_url(orig[1], [w[1] for w in wps], dest[1]), ""]
    r += ["=" * 78,
          "SUGGERIMENTO: per farti portare a una singola tappa partendo da dove",
          "ti trovi, usa questo schema togliendo l'origine:",
          "https://www.google.com/maps/dir/?api=1&destination=LAT%2CLON&travelmode=driving",
          ""]
    with open(LINKS_PATH, "w", encoding="utf-8") as f:
        f.write("\n".join(r))
    return len(GIORNATE)


def coord_valida(lat, lon, dove, errori):
    if not (LAT_MIN <= lat <= LAT_MAX and LON_MIN <= lon <= LON_MAX):
        errori.append("%s: coordinate fuori bounding box (lat %s, lon %s)" % (dove, lat, lon))
        return False
    # Controllo esplicito di inversione: in questa zona la latitudine e'
    # sempre maggiore della longitudine (40-42 contro 13-15). Se non lo e',
    # i due valori sono stati scambiati.
    if lat < lon:
        errori.append("%s: LATITUDINE E LONGITUDINE SEMBRANO INVERTITE (lat %s < lon %s)"
                      % (dove, lat, lon))
        return False
    return True


def valida_csv_itinerario(errori, avvisi):
    attese = ["nome", "categoria", "giorno", "indirizzo", "latitudine", "longitudine",
              "base_notte", "bagno", "canoa", "costo_indicativo", "note",
              "fonte", "url", "data_verifica"]
    etichette = {"BALNEAZIONE AUTORIZZATA", "BAGNO DA VERIFICARE SUL POSTO",
                 "BAGNO VIETATO", "SOLO VISITA", "CANOA ORGANIZZATA"}
    with open(CSV_PATH, encoding="utf-8", newline="") as f:
        reader = csv.DictReader(f)
        if reader.fieldnames != attese:
            errori.append("CSV itinerario: intestazioni inattese -> %s" % reader.fieldnames)
            return 0
        n = 0
        for i, row in enumerate(reader, start=2):
            n += 1
            if None in row.values() or "" in [row["nome"], row["categoria"]]:
                errori.append("CSV riga %d: campi mancanti" % i)
                continue
            try:
                lat, lon = float(row["latitudine"]), float(row["longitudine"])
            except ValueError:
                errori.append("CSV riga %d (%s): coordinate non numeriche" % (i, row["nome"]))
                continue
            coord_valida(lat, lon, "CSV riga %d (%s)" % (i, row["nome"]), errori)
            et = row["bagno"].split(" (")[0].strip()
            if et not in etichette:
                errori.append("CSV riga %d (%s): etichetta bagno non valida -> '%s'"
                              % (i, row["nome"], row["bagno"]))
            if not row["fonte"].strip():
                errori.append("CSV riga %d (%s): fonte mancante" % (i, row["nome"]))
            if not row["url"].startswith("http"):
                avvisi.append("CSV riga %d (%s): url non valorizzato" % (i, row["nome"]))
            if row["data_verifica"] != "2026-08-06":
                avvisi.append("CSV riga %d (%s): data_verifica diversa" % (i, row["nome"]))
        return n


def valida_csv_budget(errori):
    with open(BUDGET_PATH, encoding="utf-8", newline="") as f:
        reader = csv.DictReader(f)
        attese = ["voce", "categoria", "dettaglio", "minimo_eur", "realistico_eur",
                  "massimo_eur", "stato_verifica"]
        if reader.fieldnames != attese:
            errori.append("CSV budget: intestazioni inattese -> %s" % reader.fieldnames)
            return 0
        n, somme = 0, [0.0, 0.0, 0.0]
        for i, row in enumerate(reader, start=2):
            n += 1
            if row["categoria"] == "TOTALE":
                continue
            for k, campo in enumerate(("minimo_eur", "realistico_eur", "massimo_eur")):
                try:
                    somme[k] += float(row[campo])
                except ValueError:
                    errori.append("CSV budget riga %d (%s): '%s' non numerico"
                                  % (i, row["voce"], campo))
        print("   somme calcolate dalle voci: min %.2f / real %.2f / max %.2f"
              % (somme[0], somme[1], somme[2]))
        return n


def valida_kml(errori, avvisi):
    ns = {"k": "http://www.opengis.net/kml/2.2"}
    root = ET.parse(KML_PATH).getroot()
    attese = {"Campeggi", "B&B", "Bagno autorizzato", "Bagno da verificare",
              "Bagno vietato", "Canoa", "Visite", "Percorsi"}
    trovate = set()
    for folder in root.iterfind(".//k:Folder", ns):
        nome = folder.find("k:name", ns)
        if nome is not None:
            trovate.add(nome.text)
    mancanti = attese - trovate
    if mancanti:
        errori.append("KML: cartelle mancanti -> %s" % sorted(mancanti))
    extra = trovate - attese
    if extra:
        avvisi.append("KML: cartelle non previste -> %s" % sorted(extra))

    n = 0
    for pm in root.iterfind(".//k:Placemark", ns):
        n += 1
        nome_el = pm.find("k:name", ns)
        nome = nome_el.text if nome_el is not None else "(senza nome)"
        vuoto = True
        for c in pm.iterfind(".//k:coordinates", ns):
            vuoto = False
            for t in c.text.split():
                p = t.split(",")
                if len(p) < 2:
                    errori.append("KML '%s': coordinata malformata '%s'" % (nome, t))
                    continue
                # nel KML l'ordine e' lon,lat
                lon, lat = float(p[0]), float(p[1])
                coord_valida(lat, lon, "KML '%s'" % nome, errori)
        if vuoto:
            errori.append("KML '%s': nessuna coordinata" % nome)
    return n


def valida_links(errori):
    with open(LINKS_PATH, encoding="utf-8") as f:
        urls = [l.strip() for l in f if l.strip().startswith("http")
                and "LAT%2CLON" not in l]
    for u in urls:
        if " " in u:
            errori.append("LINK: spazio non codificato -> %s" % u)
        if "|" in u:
            errori.append("LINK: '|' non codificato -> %s" % u)
        q = parse_qs(urlparse(u).query)
        for k in ("api", "origin", "destination", "travelmode"):
            if k not in q:
                errori.append("LINK: parametro '%s' mancante -> %s" % (k, u))
        if "waypoints" in q and len(q["waypoints"][0].split("|")) > MAX_WAYPOINT:
            errori.append("LINK: piu' di %d waypoint -> %s" % (MAX_WAYPOINT, u))
    return len(urls)


def main():
    errori, avvisi = [], []
    print("Generato link_google_maps.txt (%d link)." % genera_links())
    print("CSV itinerario: %d righe validate" % valida_csv_itinerario(errori, avvisi))
    print("CSV budget:     %d righe validate" % valida_csv_budget(errori))
    print("KML:            %d placemark validati" % valida_kml(errori, avvisi))
    print("Link:           %d URL validati" % valida_links(errori))
    print()
    for a in avvisi:
        print("AVVISO: " + a)
    if errori:
        for e in errori:
            print("ERRORE: " + e)
        print("\nVALIDAZIONE FALLITA (%d errori)." % len(errori))
        sys.exit(1)
    print("VALIDAZIONE SUPERATA: nessun errore.")


if __name__ == "__main__":
    main()
