"""
Modulo interfaccia — Interfaccia grafica dell'applicazione Comparativo Preventivi.

Contiene le classi:
  - PreventivRow:    widget riga per un singolo file preventivo nella lista
  - ScrollableFrame: frame con scrollbar verticale interna
  - App:             finestra principale dell'applicazione
"""

import os
import threading
from datetime import datetime
from tkinter import (Tk, Frame, Label, Button, StringVar, filedialog,
                     messagebox, Text, Scrollbar, END, WORD, DISABLED, NORMAL)
import tkinter as tk

from core.config import SOGLIA_ANOMALIA_PERC as SOGLIA_DEFAULT
from core.generatore import genera_comparativo


# ─────────────────────────────────────────────────────────────────
#  WIDGET RIGA PREVENTIVO
# ─────────────────────────────────────────────────────────────────

class PreventivRow(tk.Frame):
    """
    Widget riga per un singolo preventivo nella lista.

    Mostra: indice numerico | nome file (troncato) | campo nome azienda | bottone rimuovi
    Il campo nome azienda è editabile dall'utente.
    """
    # Palette colori del widget
    BG       = "#FFFFFF"
    ACCENT   = "#4F8EF7"
    TEXT     = "#1A1A2E"
    TEXT_DIM = "#6B7280"
    RED      = "#E05C5C"

    def __init__(self, parent, filepath, on_remove, index, **kwargs):
        super().__init__(parent, bg=self.BG, **kwargs)
        self.filepath  = filepath
        self.on_remove = on_remove

        self.columnconfigure(1, weight=1)

        # Numero indice (es. "01")
        tk.Label(self, text=f"  {index+1:02d}", bg=self.BG,
                 fg=self.ACCENT, font=("Consolas", 9, "bold"),
                 width=4).grid(row=0, column=0, padx=(8, 4), pady=6)

        # Nome file troncato (solo lettura)
        nome_file = os.path.basename(filepath)
        trunc = (nome_file[:28] + "…") if len(nome_file) > 30 else nome_file
        tk.Label(self, text=trunc, bg=self.BG, fg=self.TEXT_DIM,
                 font=("Consolas", 8), anchor="w",
                 width=30).grid(row=0, column=1, padx=4, pady=6, sticky="w")

        # Campo nome azienda (editabile dall'utente)
        self.nome_var = tk.StringVar(
            value=os.path.splitext(nome_file)[0].upper())
        tk.Entry(self, textvariable=self.nome_var,
                 bg="#F0F1F5", fg=self.TEXT,
                 insertbackground=self.ACCENT,
                 relief="flat", font=("Arial", 9), width=24,
                 highlightthickness=1,
                 highlightbackground="#D0D5E0",
                 highlightcolor=self.ACCENT).grid(
            row=0, column=2, padx=(4, 8), pady=6, ipady=3)

        # Bottone rimuovi (✕) — Label cliccabile per evitare grigio nativo macOS
        btn_x = tk.Label(self, text="✕", bg=self.BG, fg="#9CA3AF",
                         font=("Arial", 9, "bold"), cursor="hand2", width=2)
        btn_x.grid(row=0, column=3, padx=(0, 8))
        btn_x.bind("<Button-1>", lambda e: self.on_remove(self.filepath))
        btn_x.bind("<Enter>",    lambda e: btn_x.config(fg=self.RED))
        btn_x.bind("<Leave>",    lambda e: btn_x.config(fg="#9CA3AF"))

        # Separatore sottile tra righe
        tk.Frame(self, bg="#E5E7EB", height=1).grid(
            row=1, column=0, columnspan=4, sticky="ew")

    def get_nome_azienda(self):
        """
        Restituisce il nome azienda inserito dall'utente.
        Se vuoto, usa il nome del file senza estensione.
        """
        n = self.nome_var.get().strip()
        return n if n else os.path.splitext(
            os.path.basename(self.filepath))[0].upper()


# ─────────────────────────────────────────────────────────────────
#  FRAME SCROLLABILE
# ─────────────────────────────────────────────────────────────────

class ScrollableFrame(tk.Frame):
    """
    Frame con scrollbar verticale interna per la lista dei preventivi.
    Si ridimensiona automaticamente in altezza in base al contenuto
    (fino a un massimo di 200 pixel).
    """
    def __init__(self, parent, bg, **kwargs):
        super().__init__(parent, bg=bg, **kwargs)
        self.canvas    = tk.Canvas(self, bg=bg, highlightthickness=0)
        self.scrollbar = tk.Scrollbar(self, orient="vertical",
                                       command=self.canvas.yview)
        self.inner     = tk.Frame(self.canvas, bg=bg)
        self._win_id   = self.canvas.create_window(
            (0, 0), window=self.inner, anchor="nw")
        self.inner.bind("<Configure>", self._on_inner_resize)
        self.canvas.bind("<Configure>", self._on_canvas_resize)
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        self.canvas.pack(side="left", fill="both", expand=True)
        self.scrollbar.pack(side="right", fill="y")

    def _on_inner_resize(self, _evento):
        """Aggiorna la regione di scroll quando il contenuto cambia."""
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        altezza = self.inner.winfo_reqheight()
        self.canvas.configure(height=min(altezza, 200))

    def _on_canvas_resize(self, evento):
        """Adatta la larghezza del frame interno alla larghezza del canvas."""
        self.canvas.itemconfig(self._win_id, width=evento.width)


# ─────────────────────────────────────────────────────────────────
#  FINESTRA PRINCIPALE
# ─────────────────────────────────────────────────────────────────

class App(tk.Tk):
    """
    Finestra principale dell'applicazione Comparativo Preventivi.

    Flusso di utilizzo:
      1. Seleziona il file capitolato di riferimento
      2. Aggiungi i file preventivi delle imprese (con nome azienda editabile)
      3. Scegli la cartella di destinazione
      4. Imposta la soglia anomalie (%)
      5. Clicca "GENERA COMPARATIVO"
    """
    # ── Palette chiara (header blu scuro, resto light) ──
    BG_ROOT    = "#F5F6FA"   # sfondo generale grigio chiarissimo
    BG_CARD    = "#FFFFFF"   # card bianche
    BG_CARD2   = "#1F4E79"   # header blu scuro (solo per l'intestazione studio)
    ACCENT     = "#4F8EF7"   # azzurro accent (titoli sezione, bottoni primari)
    ACCENT_HOV = "#3A7AE8"
    TEXT       = "#1A1A2E"   # testo principale scuro
    TEXT_MUTED = "#6B7280"   # testo secondario grigio
    TEXT_DIM   = "#9CA3AF"   # testo dimmed
    GREEN      = "#16A34A"   # verde bottone genera
    GREEN_HOV  = "#15803D"
    RED        = "#E05C5C"
    ORANGE     = "#E8903A"
    BORDER     = "#E5E7EB"   # bordo grigio chiaro

    def __init__(self):
        super().__init__()
        self.title("Comparativo Preventivi — Studio di Architettura")
        self.configure(bg=self.BG_ROOT)
        self.minsize(820, 720)

        # Centra la finestra nello schermo
        w, h = 860, 760
        sw, sh = self.winfo_screenwidth(), self.winfo_screenheight()
        self.geometry(f"{w}x{h}+{(sw-w)//2}+{(sh-h)//2}")

        # Variabili di stato
        self.capitolato_path       = StringVar(value="")
        self.preventivi_data       = []   # list of {"path": str, "widget": PreventivRow}
        self.output_dir            = StringVar(value=os.path.expanduser("~/Desktop"))
        self.soglia_var            = tk.IntVar(value=SOGLIA_DEFAULT)
        self._btn_genera_abilitato = True   # flag per Label-bottone (no state=DISABLED)

        self._costruisci_ui()
        self._controlla_openpyxl()

    def _controlla_openpyxl(self):
        """Verifica che openpyxl sia installato; in caso contrario disabilita il bottone."""
        try:
            import openpyxl  # noqa: F401
        except ImportError:
            self._log("ERRORE: libreria 'openpyxl' non installata.", level="err")
            self._log("  Apri il Terminale: pip install openpyxl", level="err")
            self._btn_genera_abilitato = False
            self.btn_genera.config(bg="#AAAAAA", fg="#DDDDDD",
                                   text="⚡  GENERA COMPARATIVO  (openpyxl mancante)")

    # ── Helper costruzione UI ──

    def _card(self, parent, pady=(0, 12)):
        """Crea un contenitore 'card' con bordo sottile e sfondo scuro."""
        esterno = tk.Frame(parent, bg=self.BORDER, pady=1)
        esterno.pack(fill="x", pady=pady)
        interno = tk.Frame(esterno, bg=self.BG_CARD)
        interno.pack(fill="x", padx=1, pady=1)
        return interno

    def _etichetta_sezione(self, parent, testo):
        """Crea un'etichetta di sezione in stile accent."""
        tk.Label(parent, text=testo.upper(), bg=self.BG_ROOT,
                 fg=self.ACCENT, font=("Arial", 8, "bold"),
                 anchor="w").pack(fill="x", pady=(14, 4))

    def _bottone(self, parent, testo, comando, primario=True, **kw):
        """
        Crea un bottone colorato compatibile con macOS.

        Su macOS, tk.Button ignora il colore di sfondo (usa il renderer nativo).
        Si usa un tk.Label con binding click/hover per ottenere il colore desiderato.
        """
        bg  = self.ACCENT      if primario else "#E5E7EB"
        fg  = "white"          if primario else self.TEXT
        hov = self.ACCENT_HOV  if primario else "#D1D5DB"

        lbl = tk.Label(parent, text=testo, bg=bg, fg=fg,
                       font=("Arial", 9, "bold"), cursor="hand2", **kw)
        lbl.bind("<Button-1>",  lambda e: comando())
        lbl.bind("<Enter>",     lambda e: lbl.config(bg=hov))
        lbl.bind("<Leave>",     lambda e: lbl.config(bg=bg))
        lbl.bind("<ButtonRelease-1>", lambda e: lbl.config(bg=bg))
        return lbl

    # ── Costruzione UI ──

    def _costruisci_ui(self):
        """Costruisce tutti i widget della finestra principale."""
        # Header blu scuro con nome studio (l'unica area che rimane scura)
        hdr = tk.Frame(self, bg=self.BG_CARD2)
        hdr.pack(fill="x")
        tk.Frame(hdr, bg=self.ACCENT, height=3).pack(fill="x")
        hi = tk.Frame(hdr, bg=self.BG_CARD2)
        hi.pack(fill="x", padx=28, pady=14)
        tk.Label(hi, text="COMPARATIVO PREVENTIVI",
                 bg=self.BG_CARD2, fg="#FFFFFF",
                 font=("Arial", 16, "bold")).pack(side="left")
        tk.Label(hi, text="  —  Calvi Ceschia Viganò / Architetti Associati",
                 bg=self.BG_CARD2, fg="#BFD7ED",
                 font=("Arial", 11)).pack(side="left", pady=4)

        # Body principale (non espande — solo l'area log si estende)
        body = tk.Frame(self, bg=self.BG_ROOT)
        body.pack(fill="x", expand=False, padx=28, pady=8)

        # ── Sezione 1: Capitolato ──
        self._etichetta_sezione(body, "1  —  Capitolato di riferimento")
        c1 = self._card(body)
        c1i = tk.Frame(c1, bg=self.BG_CARD)
        c1i.pack(fill="x", padx=12, pady=10)
        self.lbl_cap = tk.Label(c1i, textvariable=self.capitolato_path,
                                bg="#F0F1F5", fg=self.TEXT_MUTED,
                                font=("Consolas", 9), anchor="w",
                                relief="flat", padx=10)
        self.lbl_cap.pack(side="left", fill="x", expand=True, ipady=7)
        self._bottone(c1i, "Sfoglia…", self._scegli_capitolato,
                      padx=14).pack(side="left", padx=(8, 0), ipady=5)

        # ── Sezione 2: Preventivi imprese ──
        self._etichetta_sezione(body, "2  —  Preventivi imprese")
        c2 = self._card(body)
        tb = tk.Frame(c2, bg=self.BG_CARD)
        tb.pack(fill="x", padx=12, pady=(10, 6))
        self._bottone(tb, "＋  Aggiungi file…",
                      self._aggiungi_preventivi, padx=12).pack(side="left", ipady=5)
        self._bottone(tb, "Rimuovi tutti", self._rimuovi_tutti,
                      primario=False, padx=10).pack(side="left", padx=8, ipady=5)
        tk.Label(tb, text="← inserisci il nome azienda per ogni file",
                 bg=self.BG_CARD, fg=self.TEXT_DIM,
                 font=("Arial", 8, "italic")).pack(side="right")

        self.scroll_frame = ScrollableFrame(c2, bg=self.BG_CARD)
        self.scroll_frame.pack(fill="x", padx=12, pady=(0, 10))
        self.lbl_vuoto = tk.Label(self.scroll_frame.inner,
                                   text="  Nessun file caricato",
                                   bg=self.BG_CARD, fg=self.TEXT_DIM,
                                   font=("Arial", 9), anchor="w")
        self.lbl_vuoto.pack(fill="x", pady=10)

        # ── Sezione 3: Cartella output ──
        self._etichetta_sezione(body, "3  —  Cartella di destinazione")
        c3 = self._card(body)
        c3i = tk.Frame(c3, bg=self.BG_CARD)
        c3i.pack(fill="x", padx=12, pady=10)
        self.lbl_out = tk.Label(c3i, textvariable=self.output_dir,
                                bg="#F0F1F5", fg=self.TEXT_MUTED,
                                font=("Consolas", 9), anchor="w",
                                relief="flat", padx=10)
        self.lbl_out.pack(side="left", fill="x", expand=True, ipady=7)
        self._bottone(c3i, "Cambia…", self._scegli_output,
                      padx=14).pack(side="left", padx=(8, 0), ipady=5)

        # ── Soglia anomalie ──
        c4 = self._card(body, pady=(0, 16))
        si = tk.Frame(c4, bg=self.BG_CARD)
        si.pack(fill="x", padx=12, pady=10)
        tk.Label(si, text="Soglia anomalie:", bg=self.BG_CARD,
                 fg=self.TEXT_MUTED, font=("Arial", 9)).pack(side="left")
        tk.Spinbox(si, from_=5, to=50, increment=5,
                   textvariable=self.soglia_var, width=4,
                   font=("Arial", 10, "bold"), fg=self.ACCENT,
                   bg="#F0F1F5", buttonbackground=self.BG_CARD,
                   relief="flat",
                   insertbackground=self.ACCENT).pack(side="left", padx=8)
        tk.Label(si, text="% — prezzi oltre questa soglia vengono evidenziati",
                 bg=self.BG_CARD, fg=self.TEXT_MUTED,
                 font=("Arial", 9)).pack(side="left")

        # ── Bottone GENERA ──
        bf = tk.Frame(self, bg=self.BG_ROOT)
        bf.pack(fill="x", padx=28, pady=(4, 8))
        # Label cliccabile per evitare il rendering nativo grigio di macOS
        self.btn_genera = tk.Label(bf, text="⚡  GENERA COMPARATIVO",
                                   bg=self.ACCENT, fg="white",
                                   font=("Arial", 13, "bold"),
                                   cursor="hand2")
        self.btn_genera.pack(fill="x", ipady=12)
        self.btn_genera.bind("<Button-1>",
                             lambda e: self._genera() if self._btn_genera_abilitato else None)
        self.btn_genera.bind("<Enter>",
                             lambda e: self.btn_genera.config(bg=self.ACCENT_HOV)
                             if self._btn_genera_abilitato else None)
        self.btn_genera.bind("<Leave>",
                             lambda e: self.btn_genera.config(bg=self.ACCENT)
                             if self._btn_genera_abilitato else None)

        # ── Area log ──
        lf = tk.Frame(self, bg=self.BG_ROOT)
        lf.pack(fill="both", expand=True, padx=28, pady=(0, 16))
        tk.Label(lf, text="LOG", bg=self.BG_ROOT, fg=self.ACCENT,
                 font=("Arial", 8, "bold"),
                 anchor="w").pack(fill="x", pady=(0, 4))
        lc = tk.Frame(lf, bg=self.BORDER, pady=1)
        lc.pack(fill="both", expand=True)
        li = tk.Frame(lc, bg=self.BORDER)
        li.pack(fill="both", expand=True, padx=1, pady=1)
        self.log_text = Text(li, height=7, font=("Consolas", 9),
                              bg="#F8F9FB", fg="#374151",
                              relief="flat", state=DISABLED,
                              wrap=WORD, padx=12, pady=8)
        sb = Scrollbar(li, command=self.log_text.yview)
        self.log_text.configure(yscrollcommand=sb.set)
        self.log_text.pack(side="left", fill="both", expand=True)
        sb.pack(side="right", fill="y")
        # Configurazione tag colori per il log (ottimizzati per sfondo chiaro)
        self.log_text.tag_config("ok",   foreground="#16A34A")
        self.log_text.tag_config("err",  foreground="#DC2626")
        self.log_text.tag_config("info", foreground="#2563EB")
        self.log_text.tag_config("warn", foreground="#D97706")

        self._log("Pronto. Carica il capitolato e i preventivi delle imprese.",
                  level="info")

    # ── Azioni ──

    def _scegli_capitolato(self):
        """Apre il dialogo per selezionare il file capitolato."""
        path = filedialog.askopenfilename(
            title="Seleziona il capitolato di riferimento",
            filetypes=[("Excel", "*.xlsx *.xls"), ("Tutti i file", "*.*")])
        if path:
            self.capitolato_path.set(path)
            self._log(f"Capitolato: {os.path.basename(path)}", level="info")

    def _aggiungi_preventivi(self):
        """Apre il dialogo per aggiungere uno o più file preventivo."""
        paths = filedialog.askopenfilenames(
            title="Seleziona uno o più preventivi",
            filetypes=[("Excel", "*.xlsx *.xls"), ("Tutti i file", "*.*")])
        esistenti = {d["path"] for d in self.preventivi_data}
        for p in paths:
            if p not in esistenti:
                self._aggiungi_riga(p)
        self._aggiorna_stato_vuoto()

    def _aggiungi_riga(self, filepath):
        """Aggiunge una riga PreventivRow alla lista."""
        idx   = len(self.preventivi_data)
        riga  = PreventivRow(self.scroll_frame.inner, filepath,
                             on_remove=self._rimuovi_path, index=idx)
        riga.pack(fill="x")
        self.preventivi_data.append({"path": filepath, "widget": riga})

    def _rimuovi_path(self, filepath):
        """Rimuove un file dalla lista e ricrea i widget con indici aggiornati."""
        self.preventivi_data = [d for d in self.preventivi_data
                                 if d["path"] != filepath]
        # Ricrea tutta la lista (N è sempre piccolo, max ~10 imprese)
        for w in self.scroll_frame.inner.winfo_children():
            w.destroy()
        self.lbl_vuoto = tk.Label(self.scroll_frame.inner,
                                   text="  Nessun file caricato",
                                   bg=self.BG_CARD, fg=self.TEXT_DIM,
                                   font=("Arial", 9), anchor="w")
        self.lbl_vuoto.pack(fill="x", pady=10)
        tmp = list(self.preventivi_data)
        self.preventivi_data = []
        for d in tmp:
            self._aggiungi_riga(d["path"])
        self._aggiorna_stato_vuoto()

    def _rimuovi_tutti(self):
        """Rimuove tutti i file preventivo dalla lista."""
        self.preventivi_data = []
        for w in self.scroll_frame.inner.winfo_children():
            w.destroy()
        self.lbl_vuoto = tk.Label(self.scroll_frame.inner,
                                   text="  Nessun file caricato",
                                   bg=self.BG_CARD, fg=self.TEXT_DIM,
                                   font=("Arial", 9), anchor="w")
        self.lbl_vuoto.pack(fill="x", pady=10)

    def _aggiorna_stato_vuoto(self):
        """Mostra/nasconde l'etichetta 'Nessun file caricato'."""
        if self.preventivi_data:
            self.lbl_vuoto.pack_forget()
        else:
            self.lbl_vuoto.pack(fill="x", pady=10)

    def _scegli_output(self):
        """Apre il dialogo per selezionare la cartella di destinazione."""
        d = filedialog.askdirectory(title="Scegli cartella di destinazione")
        if d:
            self.output_dir.set(d)

    def _log(self, msg, level="info"):
        """Scrive un messaggio nell'area log con il colore del livello indicato."""
        self.log_text.config(state=NORMAL)
        self.log_text.insert(END, msg + "\n", level)
        self.log_text.see(END)
        self.log_text.config(state=DISABLED)
        self.update_idletasks()

    def _genera(self):
        """Avvia la generazione del comparativo in un thread separato."""
        cap = self.capitolato_path.get()
        if not cap or not os.path.exists(cap):
            messagebox.showerror("Errore", "Seleziona prima il file capitolato.")
            return
        if not self.preventivi_data:
            messagebox.showerror("Errore", "Aggiungi almeno un preventivo.")
            return

        # Raccoglie path e nomi imprese dalla lista
        paths = [d["path"] for d in self.preventivi_data]
        nomi  = [d["widget"].get_nome_azienda() for d in self.preventivi_data]
        soglia = self.soglia_var.get()

        data_str    = datetime.now().strftime("%Y%m%d_%H%M")
        output_path = os.path.join(self.output_dir.get(),
                                   f"comparativo_{data_str}.xlsx")

        # Disabilita il bottone durante l'elaborazione
        self._btn_genera_abilitato = False
        self.btn_genera.config(text="⏳  Elaborazione in corso…",
                               bg=self.ORANGE)
        self._log("─" * 55, level="info")
        self._log(f"Avvio — soglia {soglia}%  |  "
                  f"{len(paths)} imprese: {', '.join(nomi)}", level="info")

        def esegui_in_background():
            """Funzione eseguita nel thread di background."""
            try:
                genera_comparativo(cap, paths, nomi, output_path,
                                   log_fn=self._log,
                                   soglia_anomalie=soglia)
                self.after(0, lambda: self._genera_completato(output_path))
            except Exception as e:
                import traceback
                tb = traceback.format_exc()
                self.after(0, lambda: self._genera_errore(str(e) + "\n\n" + tb))

        threading.Thread(target=esegui_in_background, daemon=True).start()

    def _genera_completato(self, output_path):
        """Callback chiamata dal thread principale al completamento."""
        self._btn_genera_abilitato = True
        self.btn_genera.config(text="⚡  GENERA COMPARATIVO",
                               bg=self.ACCENT)
        self._log(f"Completato: {output_path}", level="ok")
        if messagebox.askyesno("Completato",
                                f"Comparativo generato!\n\n{output_path}"
                                f"\n\nAprire il file ora?"):
            try:
                os.startfile(output_path)
            except AttributeError:
                import subprocess
                subprocess.run(["open", output_path])

    def _genera_errore(self, err):
        """Callback chiamata dal thread principale in caso di errore."""
        self._btn_genera_abilitato = True
        self.btn_genera.config(text="⚡  GENERA COMPARATIVO",
                               bg=self.ACCENT)
        self._log(f"ERRORE: {err}", level="err")
        messagebox.showerror("Errore",
                              f"Si è verificato un errore:\n\n{err[:400]}")
