"""
Diff Viewer - Visualizza due versioni di un file affiancate con minimappa delle differenze.
"""
import tkinter as tk
from tkinter import ttk, scrolledtext
import difflib
from pathlib import Path
from typing import Optional, List, Tuple


class DiffViewer(tk.Toplevel):
    """Finestra per visualizzare le differenze tra due file affiancati."""
    
    def __init__(self, parent, file_a_path: Optional[str], file_b_path: Optional[str], 
                 title_a: str = "Baseline", title_b: str = "Current"):
        super().__init__(parent)
        
        self.file_a_path = file_a_path
        self.file_b_path = file_b_path
        self.title_a = title_a
        self.title_b = title_b
        
        # Configurazione finestra
        self.title("Diff Viewer")
        self.geometry("1400x800")
        
        # Centro la finestra rispetto al parent
        self.transient(parent)
        self._center_window()
        
        # Carica i contenuti dei file
        self.lines_a = self._load_file(file_a_path) if file_a_path else []
        self.lines_b = self._load_file(file_b_path) if file_b_path else []
        
        # Calcola le differenze
        self.diff_blocks = self._compute_diff_blocks()
        
        # Costruisci UI
        self._build_ui()
        
        # Popola i contenuti
        self._populate_content()
        
        # Sincronizza scroll
        self._setup_scroll_sync()
        
    def _center_window(self):
        """Centra la finestra sullo schermo."""
        self.update_idletasks()
        width = self.winfo_width()
        height = self.winfo_height()
        x = (self.winfo_screenwidth() // 2) - (width // 2)
        y = (self.winfo_screenheight() // 2) - (height // 2)
        self.geometry(f'{width}x{height}+{x}+{y}')
    
    def _load_file(self, filepath: str) -> List[str]:
        """Carica il contenuto di un file."""
        try:
            with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
                return f.readlines()
        except Exception:
            return []
    
    def _compute_diff_blocks(self) -> List[Tuple[str, int, int, int, int]]:
        """
        Calcola i blocchi di differenze tra i due file.
        Ritorna una lista di tuple (tag, i1, i2, j1, j2) dove:
        - tag: 'equal', 'delete', 'insert', 'replace'
        - i1, i2: range di linee nel file A
        - j1, j2: range di linee nel file B
        """
        sm = difflib.SequenceMatcher(a=self.lines_a, b=self.lines_b)
        return sm.get_opcodes()
    
    def _build_ui(self):
        """Costruisce l'interfaccia utente."""
        # Frame principale con 3 colonne: file A, minimappa, file B
        main_frame = ttk.Frame(self)
        main_frame.pack(fill="both", expand=True, padx=5, pady=5)
        
        # Configurazione griglia: colonna 0 e 2 espandibili, colonna 1 (minimappa) fissa
        main_frame.columnconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=0, minsize=60)
        main_frame.columnconfigure(2, weight=1)
        main_frame.rowconfigure(1, weight=1)
        
        # Headers
        header_a = ttk.Label(main_frame, text=f"{self.title_a}: {Path(self.file_a_path).name if self.file_a_path else 'N/A'}", 
                             font=('Arial', 10, 'bold'))
        header_a.grid(row=0, column=0, sticky='ew', padx=2, pady=2)
        
        minimap_header = ttk.Label(main_frame, text="Diff Map", font=('Arial', 9, 'bold'))
        minimap_header.grid(row=0, column=1, sticky='ew', padx=2, pady=2)
        
        header_b = ttk.Label(main_frame, text=f"{self.title_b}: {Path(self.file_b_path).name if self.file_b_path else 'N/A'}", 
                             font=('Arial', 10, 'bold'))
        header_b.grid(row=0, column=2, sticky='ew', padx=2, pady=2)
        
        # Text widgets per file A e B con scrollbar
        self.text_a = tk.Text(main_frame, wrap='none', width=60, height=40, 
                              font=('Courier', 9), bg='#f8f8f8', state='disabled')
        self.text_a.grid(row=1, column=0, sticky='nsew', padx=(2, 1))
        
        # Scrollbar verticale condivisa
        scrollbar_v = ttk.Scrollbar(main_frame, orient='vertical')
        scrollbar_v.grid(row=1, column=1, sticky='ns', padx=0)
        
        self.text_b = tk.Text(main_frame, wrap='none', width=60, height=40, 
                              font=('Courier', 9), bg='#f8f8f8', state='disabled')
        self.text_b.grid(row=1, column=2, sticky='nsew', padx=(1, 2))
        
        # Canvas per minimappa
        self.minimap = tk.Canvas(main_frame, width=50, bg='#e8e8e8', highlightthickness=1, 
                                 highlightbackground='#ccc')
        self.minimap.grid(row=1, column=1, sticky='nsew', padx=2, pady=0)
        
        # Scrollbar orizzontali
        scrollbar_h_a = ttk.Scrollbar(main_frame, orient='horizontal', command=self.text_a.xview)
        scrollbar_h_a.grid(row=2, column=0, sticky='ew', padx=(2, 1))
        self.text_a.config(xscrollcommand=scrollbar_h_a.set)
        
        scrollbar_h_b = ttk.Scrollbar(main_frame, orient='horizontal', command=self.text_b.xview)
        scrollbar_h_b.grid(row=2, column=2, sticky='ew', padx=(1, 2))
        self.text_b.config(xscrollcommand=scrollbar_h_b.set)
        
        # Collega scrollbar verticale a entrambi i text widget
        self.text_a.config(yscrollcommand=self._on_scroll_a)
        self.text_b.config(yscrollcommand=self._on_scroll_b)
        scrollbar_v.config(command=self._on_scrollbar)
        
        # Tag per colorare le differenze
        self._configure_tags()
        
        # Info bar in basso
        info_frame = ttk.Frame(self)
        info_frame.pack(fill='x', padx=5, pady=(0, 5))
        
        self.info_label = ttk.Label(info_frame, text="", font=('Arial', 9))
        self.info_label.pack(side='left')
        
        close_btn = ttk.Button(info_frame, text="Close", command=self.destroy)
        close_btn.pack(side='right', padx=5)
    
    def _configure_tags(self):
        """Configura i tag per evidenziare le differenze."""
        # File A: linee cancellate in rosso chiaro
        self.text_a.tag_configure('delete', background='#ffcccc', foreground='#800000')
        self.text_a.tag_configure('replace', background='#ffffcc', foreground='#804000')
        self.text_a.tag_configure('equal', background='#f8f8f8')
        
        # File B: linee aggiunte in verde chiaro
        self.text_b.tag_configure('insert', background='#ccffcc', foreground='#008000')
        self.text_b.tag_configure('replace', background='#ffffcc', foreground='#804000')
        self.text_b.tag_configure('equal', background='#f8f8f8')
        
        # Numeri di riga
        self.text_a.tag_configure('linenum', foreground='#888888', font=('Courier', 8))
        self.text_b.tag_configure('linenum', foreground='#888888', font=('Courier', 8))
    
    def _populate_content(self):
        """Popola i text widget con il contenuto evidenziando le differenze."""
        self.text_a.config(state='normal')
        self.text_b.config(state='normal')
        
        # Cancella contenuto precedente
        self.text_a.delete('1.0', 'end')
        self.text_b.delete('1.0', 'end')
        
        total_added = 0
        total_deleted = 0
        total_modified = 0
        
        # Processa ogni blocco di diff
        for tag, i1, i2, j1, j2 in self.diff_blocks:
            if tag == 'equal':
                # Linee uguali
                for i in range(i1, i2):
                    line_num = f"{i+1:4d} | "
                    self.text_a.insert('end', line_num, 'linenum')
                    self.text_a.insert('end', self.lines_a[i], 'equal')
                for j in range(j1, j2):
                    line_num = f"{j+1:4d} | "
                    self.text_b.insert('end', line_num, 'linenum')
                    self.text_b.insert('end', self.lines_b[j], 'equal')
            
            elif tag == 'delete':
                # Linee cancellate (solo in A)
                total_deleted += (i2 - i1)
                for i in range(i1, i2):
                    line_num = f"{i+1:4d} - "
                    self.text_a.insert('end', line_num, 'linenum')
                    self.text_a.insert('end', self.lines_a[i], 'delete')
                # Aggiungi linee vuote in B per mantenere allineamento
                for _ in range(i2 - i1):
                    self.text_b.insert('end', "     ~ \n", 'equal')
            
            elif tag == 'insert':
                # Linee aggiunte (solo in B)
                total_added += (j2 - j1)
                # Aggiungi linee vuote in A per mantenere allineamento
                for _ in range(j2 - j1):
                    self.text_a.insert('end', "     ~ \n", 'equal')
                for j in range(j1, j2):
                    line_num = f"{j+1:4d} + "
                    self.text_b.insert('end', line_num, 'linenum')
                    self.text_b.insert('end', self.lines_b[j], 'insert')
            
            elif tag == 'replace':
                # Linee modificate
                total_modified += max(i2 - i1, j2 - j1)
                max_lines = max(i2 - i1, j2 - j1)
                
                for k in range(max_lines):
                    # File A
                    if k < (i2 - i1):
                        line_num = f"{i1+k+1:4d} ~ "
                        self.text_a.insert('end', line_num, 'linenum')
                        self.text_a.insert('end', self.lines_a[i1 + k], 'replace')
                    else:
                        self.text_a.insert('end', "     ~ \n", 'equal')
                    
                    # File B
                    if k < (j2 - j1):
                        line_num = f"{j1+k+1:4d} ~ "
                        self.text_b.insert('end', line_num, 'linenum')
                        self.text_b.insert('end', self.lines_b[j1 + k], 'replace')
                    else:
                        self.text_b.insert('end', "     ~ \n", 'equal')
        
        self.text_a.config(state='disabled')
        self.text_b.config(state='disabled')
        
        # Aggiorna info label
        info_text = f"Lines: {len(self.lines_a)} → {len(self.lines_b)}  |  "
        info_text += f"Added: {total_added}  Deleted: {total_deleted}  Modified: {total_modified}"
        self.info_label.config(text=info_text)
        
        # Disegna minimappa
        self._draw_minimap()
    
    def _draw_minimap(self):
        """Disegna la minimappa con rappresentazione visiva delle differenze."""
        self.minimap.delete('all')
        
        total_lines = max(len(self.lines_a), len(self.lines_b), 1)
        canvas_height = self.minimap.winfo_height()
        if canvas_height <= 1:
            canvas_height = 600  # Default se non ancora renderizzato
        
        canvas_width = self.minimap.winfo_width()
        if canvas_width <= 1:
            canvas_width = 50
        
        # Calcola altezza per ogni linea nella minimappa
        line_height = max(1, canvas_height / total_lines)
        
        current_line = 0
        
        # Disegna ogni blocco di diff sulla minimappa
        for tag, i1, i2, j1, j2 in self.diff_blocks:
            num_lines = max(i2 - i1, j2 - j1)
            y1 = current_line * line_height
            y2 = (current_line + num_lines) * line_height
            
            # Colore basato sul tipo di differenza
            if tag == 'delete':
                color = '#ff9999'  # Rosso
            elif tag == 'insert':
                color = '#99ff99'  # Verde
            elif tag == 'replace':
                color = '#ffff99'  # Giallo
            else:
                color = '#e8e8e8'  # Grigio chiaro per linee uguali
            
            self.minimap.create_rectangle(0, y1, canvas_width, y2, fill=color, outline='')
            current_line += num_lines
        
        # Bind click sulla minimappa per navigare
        self.minimap.bind('<Button-1>', self._on_minimap_click)
    
    def _on_minimap_click(self, event):
        """Gestisce il click sulla minimappa per navigare."""
        canvas_height = self.minimap.winfo_height()
        total_lines = max(len(self.lines_a), len(self.lines_b), 1)
        
        # Calcola la linea cliccata
        clicked_ratio = event.y / canvas_height
        target_line = int(clicked_ratio * total_lines)
        
        # Scroll a quella linea
        self.text_a.see(f"{target_line}.0")
        self.text_b.see(f"{target_line}.0")
    
    def _setup_scroll_sync(self):
        """Configura la sincronizzazione dello scroll tra i due text widget."""
        # Bind mouse wheel
        self.text_a.bind('<MouseWheel>', self._on_mousewheel)
        self.text_b.bind('<MouseWheel>', self._on_mousewheel)
        
        # Bind arrow keys
        self.text_a.bind('<Up>', lambda e: self._scroll_both('scroll', -1, 'units'))
        self.text_a.bind('<Down>', lambda e: self._scroll_both('scroll', 1, 'units'))
        self.text_b.bind('<Up>', lambda e: self._scroll_both('scroll', -1, 'units'))
        self.text_b.bind('<Down>', lambda e: self._scroll_both('scroll', 1, 'units'))
    
    def _on_scroll_a(self, *args):
        """Callback per scroll del text A."""
        self.text_b.yview_moveto(args[0])
        self._update_minimap_viewport()
    
    def _on_scroll_b(self, *args):
        """Callback per scroll del text B."""
        self.text_a.yview_moveto(args[0])
        self._update_minimap_viewport()
    
    def _on_scrollbar(self, *args):
        """Callback per scrollbar condivisa."""
        self.text_a.yview(*args)
        self.text_b.yview(*args)
        self._update_minimap_viewport()
    
    def _on_mousewheel(self, event):
        """Gestisce scroll con mouse wheel."""
        delta = -1 if event.delta > 0 else 1
        self._scroll_both('scroll', delta, 'units')
        return 'break'
    
    def _scroll_both(self, *args):
        """Scrolla entrambi i text widget insieme."""
        self.text_a.yview(*args)
        self.text_b.yview(*args)
        self._update_minimap_viewport()
    
    def _update_minimap_viewport(self):
        """Aggiorna il rettangolo del viewport sulla minimappa."""
        # Rimuovi rettangolo precedente
        self.minimap.delete('viewport')
        
        # Calcola posizione viewport
        yview = self.text_a.yview()
        canvas_height = self.minimap.winfo_height()
        
        y1 = yview[0] * canvas_height
        y2 = yview[1] * canvas_height
        canvas_width = self.minimap.winfo_width()
        
        # Disegna rettangolo viewport
        self.minimap.create_rectangle(0, y1, canvas_width, y2, 
                                       outline='#0066cc', width=2, tags='viewport')


def show_diff_viewer(parent, file_a_path: Optional[str], file_b_path: Optional[str], 
                     title_a: str = "Baseline", title_b: str = "Current"):
    """
    Funzione helper per mostrare il diff viewer.
    
    Args:
        parent: Widget parent (tipicamente la finestra principale)
        file_a_path: Path del file baseline (può essere None)
        file_b_path: Path del file corrente (può essere None)
        title_a: Titolo per il file A
        title_b: Titolo per il file B
    """
    viewer = DiffViewer(parent, file_a_path, file_b_path, title_a, title_b)
    viewer.grab_set()  # Rende la finestra modale
    return viewer
