"""
GUI module - tkinter-based interface for the deduplicator.
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from typing import Optional, Callable, List, Dict
import threading
import os
from pathlib import Path
from datetime import datetime

from core.scanner import FileScanner, DuplicateGroup, FileInfo
from actions.file_ops import FileManager, UndoManager


class DuplicateTreeItem:
    """Represents a duplicate group in the tree view."""
    def __init__(self, group: DuplicateGroup):
        self.group = group
        self.selected_files: List[str] = []  # Files to remove
    
    def toggle_file(self, path: str) -> None:
        """Toggle file selection."""
        if path in self.selected_files:
            self.selected_files.remove(path)
        else:
            self.selected_files.append(path)


class DeduplicatorGUI:
    """Main GUI application."""
    
    def __init__(self, root: tk.Tk):
        self.root = root
        self.root.title("File Deduplicator")
        self.root.geometry("1200x700")
        self.root.minsize(900, 600)
        
        # State
        self.scanner = FileScanner(progress_callback=self._update_progress)
        self.file_manager: Optional[FileManager] = None
        self.undo_manager = UndoManager()
        self.duplicate_groups: Dict[str, DuplicateGroup] = {}
        self.current_path: str = ""
        self.is_scanning = False
        self._group_paths: Dict[str, List[str]] = {}  # group_id -> list of full file paths
        
        # Setup UI
        self._setup_styles()
        self._create_menu()
        self._create_main_layout()
        self._create_status_bar()
        
        # Bind events
        self.root.protocol("WM_DELETE_WINDOW", self._on_close)
    
    def _setup_styles(self) -> None:
        """Configure ttk styles."""
        style = ttk.Style()
        style.theme_use('clam')
        
        # Configure colors
        style.configure("Title.TLabel", font=("Helvetica", 16, "bold"))
        style.configure("Stats.TLabel", font=("Helvetica", 11))
        style.configure("Warning.TLabel", foreground="orange")
        style.configure("Danger.TLabel", foreground="red")
        style.configure("Success.TLabel", foreground="green")
    
    def _create_menu(self) -> None:
        """Create menu bar."""
        menubar = tk.Menu(self.root)
        self.root.config(menu=menubar)
        
        # File menu
        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=file_menu)
        file_menu.add_command(label="Open Folder...", command=self._select_folder, accelerator="Ctrl+O")
        file_menu.add_separator()
        file_menu.add_command(label="Exit", command=self._on_close, accelerator="Ctrl+Q")
        
        # Actions menu
        actions_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Actions", menu=actions_menu)
        actions_menu.add_command(label="Scan for Duplicates", command=self._scan, accelerator="F5")
        actions_menu.add_command(label="Remove Selected", command=self._remove_selected, accelerator="Delete")
        actions_menu.add_separator()
        actions_menu.add_command(label="Undo Last Batch", command=self._undo_last)
        
        # View menu
        view_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="View", menu=view_menu)
        view_menu.add_command(label="Refresh", command=self._refresh_view, accelerator="F5")
        
        # Help menu
        help_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Help", menu=help_menu)
        help_menu.add_command(label="About", command=self._show_about)
        
        # Keyboard shortcuts
        self.root.bind('<Control-o>', lambda e: self._select_folder())
        self.root.bind('<Control-q>', lambda e: self._on_close())
        self.root.bind('<F5>', lambda e: self._scan())
        self.root.bind('<Delete>', lambda e: self._remove_selected())
    
    def _create_main_layout(self) -> None:
        """Create main layout."""
        # Main container
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky="nsew")
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        
        # Top section - Folder selection and stats
        top_frame = ttk.Frame(main_frame)
        top_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10))
        top_frame.columnconfigure(1, weight=1)
        
        ttk.Label(top_frame, text="Folder:", style="Title.TLabel").grid(row=0, column=0, padx=(0, 10))
        
        self.folder_var = tk.StringVar(value="No folder selected")
        folder_entry = ttk.Entry(top_frame, textvariable=self.folder_var, state="readonly", width=50)
        folder_entry.grid(row=0, column=1, sticky="ew", padx=(0, 10))
        
        ttk.Button(top_frame, text="Browse...", command=self._select_folder).grid(row=0, column=2, padx=(0, 10))
        ttk.Button(top_frame, text="Scan", command=self._scan).grid(row=0, column=3)
        
        # Stats frame
        stats_frame = ttk.LabelFrame(main_frame, text="Statistics", padding="10")
        stats_frame.grid(row=1, column=0, sticky="ew", pady=(0, 10))
        stats_frame.columnconfigure(1, weight=1)
        
        self.stats_labels = {}
        stats = [
            ("Total Files:", "total_files"),
            ("Total Size:", "total_size"),
            ("Duplicate Groups:", "duplicate_groups"),
            ("Duplicate Files:", "duplicate_files"),
            ("Wasted Space:", "wasted_space"),
        ]
        
        for i, (label, key) in enumerate(stats):
            ttk.Label(stats_frame, text=label).grid(row=i, column=0, sticky="w", padx=(0, 20))
            value_label = ttk.Label(stats_frame, text="-", style="Stats.TLabel")
            value_label.grid(row=i, column=1, sticky="w")
            self.stats_labels[key] = value_label
        
        # Middle section - Duplicate tree
        tree_frame = ttk.LabelFrame(main_frame, text="Duplicate Groups", padding="10")
        tree_frame.grid(row=2, column=0, sticky="nsew", pady=(0, 10))
        tree_frame.columnconfigure(0, weight=1)
        tree_frame.rowconfigure(0, weight=1)
        
        # Treeview with scrollbar
        columns = ("size", "count", "waste", "select")
        self.tree = ttk.Treeview(tree_frame, columns=columns, show="tree headings", selectmode="extended")
        self.tree.heading("#0", text="Files")
        self.tree.heading("size", text="Size")
        self.tree.heading("count", text="Count")
        self.tree.heading("waste", text="Wasted")
        self.tree.heading("select", text="Action")
        
        self.tree.column("#0", width=400, minwidth=300)
        self.tree.column("size", width=80, anchor="e")
        self.tree.column("count", width=60, anchor="center")
        self.tree.column("waste", width=80, anchor="e")
        self.tree.column("select", width=80, anchor="center")
        
        scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        
        self.tree.grid(row=0, column=0, sticky="nsew")
        scrollbar.grid(row=0, column=1, sticky="ns")
        
        # Bind tree selection
        self.tree.bind("<<TreeviewSelect>>", self._on_tree_select)
        
        # Bottom section - Action buttons
        action_frame = ttk.Frame(main_frame)
        action_frame.grid(row=3, column=0, sticky="ew")
        
        self.select_all_btn = ttk.Button(action_frame, text="Select All in Group", command=self._select_all_in_group)
        self.select_all_btn.grid(row=0, column=0, padx=(0, 10))
        
        self.select_keep_newest = ttk.Button(action_frame, text="Keep Newest", command=self._keep_newest)
        self.select_keep_newest.grid(row=0, column=1, padx=(0, 10))
        
        self.select_keep_oldest = ttk.Button(action_frame, text="Keep Oldest", command=self._keep_oldest)
        self.select_keep_oldest.grid(row=0, column=2, padx=(0, 10))
        
        self.remove_btn = ttk.Button(action_frame, text="Remove Selected", command=self._remove_selected)
        self.remove_btn.grid(row=0, column=3, padx=(0, 10))
        self.remove_btn.state(['disabled'])
        
        self.undo_btn = ttk.Button(action_frame, text="Undo Last", command=self._undo_last)
        self.undo_btn.grid(row=0, column=4)
        self.undo_btn.state(['disabled'])
        
        # Progress bar
        self.progress_frame = ttk.Frame(main_frame)
        self.progress_frame.grid(row=4, column=0, sticky="ew", pady=(10, 0))
        
        self.progress = ttk.Progressbar(self.progress_frame, mode='indeterminate')
        self.progress.pack(fill="x")
        self.progress_label = ttk.Label(self.progress_frame, text="")
        self.progress_label.pack()
    
    def _create_status_bar(self) -> None:
        """Create status bar."""
        self.status_var = tk.StringVar(value="Ready")
        status_bar = ttk.Label(self.root, textvariable=self.status_var, relief="sunken", anchor="w")
        status_bar.grid(row=1, column=0, sticky="ew")
    
    def _select_folder(self) -> None:
        """Open folder selection dialog."""
        folder = filedialog.askdirectory(title="Select folder to scan")
        if folder:
            self.current_path = folder
            self.folder_var.set(folder)
            self.status_var.set(f"Selected: {folder}")
    
    def _scan(self) -> None:
        """Scan for duplicates."""
        if not self.current_path:
            messagebox.showwarning("No Folder", "Please select a folder first.")
            return
        
        if self.is_scanning:
            return
        
        self.is_scanning = True
        self.progress.start()
        self.status_var.set("Scanning...")
        self.remove_btn.state(['disabled'])
        
        # Run scan in background thread
        thread = threading.Thread(target=self._run_scan, daemon=True)
        thread.start()
    
    def _run_scan(self) -> None:
        """Run the scan (in background thread)."""
        try:
            self.duplicate_groups = self.scanner.scan_directory(self.current_path)
            
            # Update UI in main thread
            self.root.after(0, self._update_scan_results)
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("Scan Error", str(e)))
        finally:
            self.is_scanning = False
            self.root.after(0, self._stop_progress)
    
    def _update_scan_results(self) -> None:
        """Update UI with scan results."""
        # Update stats
        stats = self.scanner.get_statistics()
        self.stats_labels["total_files"].config(text=f"{stats['total_files']:,}")
        self.stats_labels["total_size"].config(text=self._format_size(stats['total_size']))
        self.stats_labels["duplicate_groups"].config(text=str(stats['duplicate_groups']))
        self.stats_labels["duplicate_files"].config(text=str(stats['duplicate_files']))
        self.stats_labels["wasted_space"].config(text=self._format_size(stats['wasted_space']))
        
        # Populate tree
        self._populate_tree()
        
        self.status_var.set(f"Found {stats['duplicate_groups']} duplicate groups")
        if stats['duplicate_groups'] > 0:
            self.remove_btn.state(['!disabled'])
    
    def _populate_tree(self) -> None:
        """Populate tree view with duplicate groups."""
        # Clear existing items
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        # Reset path tracking
        self._group_paths = {}
        
        # Add duplicate groups
        for idx, (hash_val, group) in enumerate(self.duplicate_groups.items()):
            # Create group header (NOT a file - just metadata)
            group_id = f"group_{idx}"
            waste = self._format_size(group.total_waste())
            
            # Store all file paths for this group
            self._group_paths[group_id] = [f.path for f in group.files]
            
            # Sort files by modified time (newest first) for consistent display
            sorted_files = sorted(group.files, key=lambda f: f.modified, reverse=True)
            newest_file = sorted_files[0] if sorted_files else None
            
            # Group header shows metadata
            header_text = f"Group {idx + 1}: {len(group.files)} files, {self._format_size(group.size)} each, {waste} wasted"
            self.tree.insert("", "end", iid=group_id, text=header_text, 
                           values=(self._format_size(group.size), len(group.files), waste, ""),
                           tags=(f"group_{hash_val}",))  # Mark as group header
            
            # Add ALL files as children (including first file)
            for file_info in sorted_files:
                child_id = f"file_{id(file_info)}"
                display_path = self._truncate_path(file_info.path, 60)
                
                # Mark kept file (newest) with ★ symbol
                is_kept = (file_info == newest_file)
                prefix = "★ " if is_kept else "  "
                
                self.tree.insert(group_id, "end", iid=child_id, text=prefix + display_path,
                               values=("", "", "", "☐"),
                               tags=(file_info.path,))  # Store full path in tags
    
    def _truncate_path(self, path: str, max_len: int) -> str:
        """Truncate path for display."""
        if len(path) <= max_len:
            return path
        
        # Show beginning and end
        keep_start = max_len // 2 - 2
        keep_end = max_len // 2 - 2
        return f"{path[:keep_start]}...{path[-keep_end:]}"
    
    def _format_size(self, size: int) -> str:
        """Format size in human-readable format."""
        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if size < 1024:
                return f"{size:.1f} {unit}"
            size /= 1024
        return f"{size:.1f} PB"
    
    def _update_progress(self, phase: str, current: int, total: int) -> None:
        """Update progress indicator."""
        if phase == "complete":
            self.root.after(0, lambda: self.progress_label.config(text="Complete!"))
        else:
            pct = (current / total * 100) if total > 0 else 0
            self.root.after(0, lambda: self.progress_label.config(text=f"{phase.capitalize()}: {current:,}/{total:,} ({pct:.1f}%)"))
    
    def _stop_progress(self) -> None:
        """Stop progress indicator."""
        self.progress.stop()
        self.progress_label.config(text="")
    
    def _on_tree_select(self, event) -> None:
        """Handle tree selection."""
        selected = self.tree.selection()
        if selected:
            self.select_all_btn.state(['!disabled'])
        else:
            self.select_all_btn.state(['disabled'])
    
    def _select_all_in_group(self) -> None:
        """Select all files in selected groups except the first (keep one)."""
        selected = self.tree.selection()
        for item_id in selected:
            # Check if it's a group item
            if item_id.startswith("group_"):
                # Select all children (files to remove)
                children = self.tree.get_children(item_id)
                for child in children:
                    self.tree.selection_add(child)
    
    def _keep_newest(self) -> None:
        """Auto-select to keep newest file in each group (select older for removal)."""
        groups_processed = 0
        
        for group_id, group in self.duplicate_groups.items():
            if len(group.files) < 2:
                continue
            
            # Sort by modified time (newest first)
            sorted_files = sorted(group.files, key=lambda f: f.modified, reverse=True)
            
            # Select all except newest for removal
            for file_info in sorted_files[1:]:
                # Find and select the tree item by matching path in tags
                for child_id in self.tree.get_children(group_id):
                    item_tags = self.tree.item(child_id, "tags")
                    if item_tags and file_info.path in item_tags:
                        self.tree.selection_add(child_id)
                        break
            
            groups_processed += 1
        
        if groups_processed > 0:
            messagebox.showinfo(
                "Keep Newest",
                f"Selected {groups_processed} group(s) for removal.\n"
                f"Keeping the newest file in each group."
            )
        else:
            messagebox.showinfo(
                "Keep Newest",
                "No duplicate groups with multiple files found."
            )
    
    def _keep_oldest(self) -> None:
        """Auto-select to keep oldest file in each group (select newer for removal)."""
        groups_processed = 0
        
        for group_id, group in self.duplicate_groups.items():
            if len(group.files) < 2:
                continue
            
            # Sort by modified time (oldest first)
            sorted_files = sorted(group.files, key=lambda f: f.modified)
            
            # Select all except oldest for removal
            for file_info in sorted_files[1:]:
                # Find and select the tree item by matching path in tags
                for child_id in self.tree.get_children(group_id):
                    item_tags = self.tree.item(child_id, "tags")
                    if item_tags and file_info.path in item_tags:
                        self.tree.selection_add(child_id)
                        break
            
            groups_processed += 1
        
        if groups_processed > 0:
            messagebox.showinfo(
                "Keep Oldest",
                f"Selected {groups_processed} group(s) for removal.\n"
                f"Keeping the oldest file in each group."
            )
        else:
            messagebox.showinfo(
                "Keep Oldest",
                "No duplicate groups with multiple files found."
            )
    
    def _remove_selected(self) -> None:
        """Remove selected files."""
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("No Selection", "Please select files to remove.")
            return
        
        # Get full paths from tags (skip group headers)
        files_to_remove = []
        for item_id in selected:
            # Skip group headers (they start with "group_")
            if item_id.startswith("group_"):
                continue
            
            item_tags = self.tree.item(item_id, "tags")
            if item_tags and len(item_tags) > 0:
                full_path = item_tags[0]
                # Validate it's a real path, not a group hash
                if full_path and not full_path.startswith("group_"):
                    files_to_remove.append(full_path)
        
        if not files_to_remove:
            messagebox.showwarning("No Files", "No files selected for removal.\n\nNote: Click on individual files to select them, not group headers.")
            return
        
        # Calculate total size
        total_size = sum(
            os.path.getsize(f) for f in files_to_remove if os.path.exists(f)
        )
        
        # Confirm
        result = messagebox.askyesno(
            "Confirm",
            f"Move {len(files_to_remove)} file(s) to trash?\n"
            f"Total size: {self._format_size(total_size)}\n\n"
            f"This can be undone.",
            icon=messagebox.WARNING
        )
        
        if not result:
            return
        
        # Start batch operation
        self.file_manager = FileManager(undo_manager=self.undo_manager, dry_run=False)
        batch_id = self.file_manager.start_batch(f"Removed {len(files_to_remove)} duplicates")
        
        removed_count = 0
        for full_path in files_to_remove:
            if self.file_manager.delete_file(full_path):
                removed_count += 1
                # Remove from tree (find by tag)
                for group_id in self.tree.get_children():
                    for child_id in self.tree.get_children(group_id):
                        item_tags = self.tree.item(child_id, "tags")
                        if item_tags and full_path in item_tags:
                            self.tree.delete(child_id)
                            break
        
        # Update undo button
        self.undo_btn.state(['!disabled'])
        
        # Refresh view to update stats and remove empty groups
        self._refresh_view()
        
        # Show results
        messagebox.showinfo("Complete", f"Moved {removed_count} file(s) to trash.\n\nBatch ID: {batch_id}")
    
    def _undo_last(self) -> None:
        """Undo last batch of operations."""
        batches = self.undo_manager.get_recent_batches()
        if not batches:
            messagebox.showinfo("Nothing to Undo", "No operations to undo.")
            return
        
        # Undo most recent batch
        last_batch = batches[0]
        success, messages = self.undo_manager.undo_batch(last_batch.batch_id)
        
        if success:
            messagebox.showinfo("Undo Complete", f"Restored files from: {last_batch.description}")
            self._refresh_view()
        else:
            messagebox.showwarning("Undo Partial", "\n".join(messages))
        
        if not self.undo_manager.get_recent_batches():
            self.undo_btn.state(['disabled'])
    
    def _refresh_view(self) -> None:
        """Refresh the view."""
        if self.current_path:
            self._scan()
    
    def _show_about(self) -> None:
        """Show about dialog."""
        messagebox.showinfo(
            "About File Deduplicator",
            "File Deduplicator v1.0\n\n"
            "Find and remove duplicate files safely.\n\n"
            "Features:\n"
            "• SHA256 content hashing\n"
            "• Safe operations with undo\n"
            "• Preview before removal\n"
            "• Batch operations"
        )
    
    def _on_close(self) -> None:
        """Handle window close."""
        if self.is_scanning:
            if not messagebox.askyesno("Scanning in Progress", "Scan in progress. Close anyway?"):
                return
        self.root.destroy()


def run_gui() -> None:
    """Run the GUI application."""
    root = tk.Tk()
    app = DeduplicatorGUI(root)
    root.mainloop()


if __name__ == "__main__":
    run_gui()
