"""GUI interface for file-organizer.

This module provides a Tkinter-based graphical user interface for organizing files
by type, date, or size. It wraps the core FileOrganizer functionality with
a user-friendly desktop application.

Requires: tkinter (built into Python standard library)
"""

from __future__ import annotations

import os
import tkinter as tk
from pathlib import Path
from tkinter import filedialog, messagebox, ttk

from .config import Config, load_config
from .core import FileOrganizer, organize_directory
from .models import OrganizeMode, OrganizePlan, UndoLog


class FileOrganizerGUI:
    """Main GUI application for file-organizer."""

    def __init__(self, root: tk.Tk):
        """Initialize the GUI.

        Args:
            root: Tkinter root window.
        """
        self.root = root
        self.root.title("File Organizer")
        self.root.geometry("800x600")
        self.root.minsize(700, 500)

        # Start maximized / full screen
        self._maximize_window()

        # State
        self.config = Config(load_config())
        self.plan: OrganizePlan | None = None
        self.current_path: Path | None = None

        # Build UI
        self._create_widgets()
        self._layout_widgets()

    def _maximize_window(self) -> None:
        """Maximize the window on startup (cross-platform)."""
        try:
            # Windows
            self.root.state("zoomed")
        except tk.TclError:
            try:
                # Linux/macOS
                self.root.attributes("-zoomed", True)
            except tk.TclError:
                # Fallback: set to screen size
                self.root.update_idletasks()
                width = self.root.winfo_screenwidth()
                height = self.root.winfo_screenheight()
                self.root.geometry(f"{width}x{height}+0+0")

    def _create_widgets(self) -> None:
        """Create all UI widgets."""
        # Main container with padding
        self.main_frame = ttk.Frame(self.root, padding="10")
        self.main_frame.columnconfigure(0, weight=1)
        self.main_frame.rowconfigure(2, weight=1)

        # --- Top: Directory Selection ---
        self.dir_frame = ttk.LabelFrame(self.main_frame, text="Directory to Organize", padding="10")
        self.dir_frame.columnconfigure(1, weight=1)

        self.path_var = tk.StringVar()
        self.path_entry = ttk.Entry(
            self.dir_frame, textvariable=self.path_var, state="readonly"
        )
        self.browse_btn = ttk.Button(
            self.dir_frame, text="Browse...", command=self._browse_directory
        )

        # --- Middle: Options ---
        self.options_frame = ttk.LabelFrame(
            self.main_frame, text="Organization Options", padding="10"
        )
        self.options_frame.columnconfigure(1, weight=1)

        # Mode selection
        ttk.Label(self.options_frame, text="Organize by:").grid(
            row=0, column=0, sticky="w", padx=5, pady=5
        )
        self.mode_var = tk.StringVar(value="type")
        self.mode_combo = ttk.Combobox(
            self.options_frame,
            textvariable=self.mode_var,
            values=["type", "date", "size"],
            state="readonly",
            width=15
        )
        self.mode_combo.grid(row=0, column=1, sticky="w", padx=5, pady=5)

        # Recursive checkbox
        self.recursive_var = tk.BooleanVar()
        self.recursive_check = ttk.Checkbutton(
            self.options_frame,
            text="Include subdirectories (recursive)",
            variable=self.recursive_var
        )
        self.recursive_check.grid(row=1, column=0, columnspan=2, sticky="w", padx=5, pady=5)

        # Export plan checkbox
        self.export_var = tk.BooleanVar()
        self.export_check = ttk.Checkbutton(
            self.options_frame,
            text="Export plan to JSON file",
            variable=self.export_var
        )
        self.export_check.grid(row=2, column=0, columnspan=2, sticky="w", padx=5, pady=5)

        # --- Preview Area ---
        self.preview_frame = ttk.LabelFrame(self.main_frame, text="Preview", padding="10")
        self.preview_frame.columnconfigure(0, weight=1)
        self.preview_frame.rowconfigure(0, weight=1)

        # Treeview for file list
        columns = ("#", "Source", "Destination", "Size")
        self.tree = ttk.Treeview(self.preview_frame, columns=columns, show="headings", height=15)
        for col in columns:
            self.tree.heading(col, text=col)
            if col == "#":
                self.tree.column(col, width=50, anchor="center")
            elif col == "Size":
                self.tree.column(col, width=100, anchor="e")
            else:
                self.tree.column(col, width=250, anchor="w")

        # Scrollbars for tree
        self.tree_scroll_y = ttk.Scrollbar(
            self.preview_frame, orient="vertical", command=self.tree.yview
        )
        self.tree_scroll_x = ttk.Scrollbar(
            self.preview_frame, orient="horizontal", command=self.tree.xview
        )
        self.tree.configure(
            yscrollcommand=self.tree_scroll_y.set, xscrollcommand=self.tree_scroll_x.set
        )

        # Summary labels
        self.summary_var = tk.StringVar(value="No directory selected")
        self.summary_label = ttk.Label(
            self.preview_frame, textvariable=self.summary_var, font=("", 10, "bold")
        )

        # Category summary
        self.cat_frame = ttk.LabelFrame(
            self.preview_frame, text="Summary by Category", padding="5"
        )
        self.cat_frame.columnconfigure(0, weight=1)
        self.cat_tree = ttk.Treeview(
            self.cat_frame, columns=("Category", "Files", "Size"), show="headings", height=6
        )
        self.cat_tree.heading("Category", text="Category")
        self.cat_tree.heading("Files", text="Files")
        self.cat_tree.heading("Size", text="Total Size")
        self.cat_tree.column("Category", width=200)
        self.cat_tree.column("Files", width=80, anchor="center")
        self.cat_tree.column("Size", width=120, anchor="e")
        self.cat_scroll = ttk.Scrollbar(
            self.cat_frame, orient="vertical", command=self.cat_tree.yview
        )
        self.cat_tree.configure(yscrollcommand=self.cat_scroll.set)

        # --- Bottom: Action Buttons ---
        self.btn_frame = ttk.Frame(self.main_frame)
        self.btn_frame.columnconfigure(0, weight=1)
        self.btn_frame.columnconfigure(1, weight=1)
        self.btn_frame.columnconfigure(2, weight=1)

        self.preview_btn = ttk.Button(
            self.btn_frame, text="Preview (Dry Run)", command=self._do_preview
        )
        self.apply_btn = ttk.Button(
            self.btn_frame, text="Apply Changes", command=self._do_apply, state="disabled"
        )
        self.undo_btn = ttk.Button(
            self.btn_frame, text="Undo Last", command=self._do_undo
        )

        # Progress bar (hidden initially)
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(
            self.main_frame, variable=self.progress_var, mode="determinate"
        )
        self.progress_label = ttk.Label(self.main_frame, text="")

        # Status bar
        self.status_var = tk.StringVar(value="Ready")
        self.status_bar = ttk.Label(
            self.root, textvariable=self.status_var, relief="sunken", anchor="w"
        )

    def _layout_widgets(self) -> None:
        """Layout all widgets using grid."""
        # Main frame fills root
        self.main_frame.grid(row=0, column=0, sticky="nsew")
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)

        # Directory frame
        self.dir_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10))
        self.path_entry.grid(row=0, column=1, sticky="ew", padx=5)
        self.browse_btn.grid(row=0, column=2, padx=5)

        # Options frame
        self.options_frame.grid(row=1, column=0, sticky="ew", pady=(0, 10))

        # Preview frame
        self.preview_frame.grid(row=2, column=0, sticky="nsew", pady=(0, 10))
        self.tree.grid(row=0, column=0, sticky="nsew")
        self.tree_scroll_y.grid(row=0, column=1, sticky="ns")
        self.tree_scroll_x.grid(row=1, column=0, sticky="ew")
        self.summary_label.grid(row=2, column=0, sticky="w", pady=(5, 5))
        self.cat_frame.grid(row=3, column=0, sticky="ew", pady=(5, 0))
        self.cat_tree.grid(row=0, column=0, sticky="ew")
        self.cat_scroll.grid(row=0, column=1, sticky="ns")

        # Button frame
        self.btn_frame.grid(row=3, column=0, sticky="ew", pady=(0, 10))
        self.preview_btn.grid(row=0, column=0, padx=5, sticky="ew")
        self.apply_btn.grid(row=0, column=1, padx=5, sticky="ew")
        self.undo_btn.grid(row=0, column=2, padx=5, sticky="ew")

        # Progress bar (initially hidden)
        self.progress_bar.grid(row=4, column=0, sticky="ew", pady=(0, 5))
        self.progress_label.grid(row=5, column=0, sticky="w")
        self.progress_bar.grid_remove()
        self.progress_label.grid_remove()

        # Status bar at bottom of root
        self.status_bar.grid(row=1, column=0, sticky="ew")

    def _browse_directory(self) -> None:
        """Open directory selection dialog."""
        path = filedialog.askdirectory(title="Select Directory to Organize")
        if path:
            self.current_path = Path(path)
            self.path_var.set(str(path))
            self._update_status(f"Selected: {path}")

    def _update_status(self, message: str) -> None:
        """Update status bar message."""
        self.status_var.set(message)
        self.root.update_idletasks()

    def _format_size(self, size_bytes: int) -> str:
        """Format bytes as human-readable string."""
        size_float = float(size_bytes)
        for unit in ["B", "KB", "MB", "GB", "TB"]:
            if size_float < 1024:
                return f"{size_float:.1f} {unit}"
            size_float /= 1024
        return f"{size_float:.1f} PB"

    def _do_preview(self) -> None:
        """Generate and display organization preview."""
        if not self.current_path:
            messagebox.showwarning("No Directory", "Please select a directory first.")
            return

        try:
            mode = OrganizeMode(self.mode_var.get())
            self._update_status("Generating preview...")

            # Always create plan with dry_run=True for preview
            self.plan, _ = organize_directory(
                self.current_path,
                mode,
                dry_run=True,
                config=self.config
            )

            if self.recursive_var.get():
                # Extend plan with subdirectories
                organizer = FileOrganizer(self.config)
                # Temporarily disable subdirs for flat category structure
                original_create_subdirs = self.config._data["organize"]["create_subdirs"]
                self.config._data["organize"]["create_subdirs"] = False
                try:
                    for subdir in self.current_path.rglob("*"):
                        if subdir.is_dir() and subdir != self.current_path:
                            sub_plan = organizer.plan_organize(subdir, mode, True)
                            for action in sub_plan.actions:
                                self.plan.add_action(
                                    action.source, action.destination, action.size_bytes
                                )
                finally:
                    self.config._data["organize"]["create_subdirs"] = original_create_subdirs

            # Populate preview tree
            self._populate_preview()

            # Enable apply button
            self.apply_btn.config(state="normal")

            if self.plan.total_files() == 0:
                self._update_status("No files to organize")
                messagebox.showinfo("Preview", "No files to organize in the selected directory.")
            else:
                total_files = self.plan.total_files()
                total_size = self._format_size(self.plan.total_size())
                self._update_status(f"Preview ready: {total_files} files, {total_size}")

        except Exception as e:
            self._update_status(f"Error: {e}")
            messagebox.showerror("Preview Error", str(e))

    def _populate_preview(self) -> None:
        """Populate the preview tree with plan actions."""
        # Clear existing items
        for item in self.tree.get_children():
            self.tree.delete(item)
        for item in self.cat_tree.get_children():
            self.cat_tree.delete(item)

        if not self.plan:
            return

        # Add file actions to tree
        for i, action in enumerate(self.plan.actions, 1):
            src_name = action.source.name
            dst_name = action.destination.name
            size_str = self._format_size(action.size_bytes)
            self.tree.insert("", "end", values=(i, src_name, dst_name, size_str))

        # Update summary
        mode_str = self.plan.mode.value.capitalize()
        dry_run_str = " (Dry Run)" if self.plan.dry_run else ""
        self.summary_var.set(
            f"Mode: {mode_str} | Files: {self.plan.total_files()} | "
            f"Total Size: {self._format_size(self.plan.total_size())}{dry_run_str}"
        )

        # Populate category summary
        from collections import defaultdict
        cat_stats: dict[str, tuple[int, int]] = defaultdict(lambda: (0, 0))
        for action in self.plan.actions:
            cat = action.destination.parent.name
            count, size = cat_stats[cat]
            cat_stats[cat] = (count + 1, size + action.size_bytes)

        if len(cat_stats) > 1:
            for cat, (count, size) in sorted(cat_stats.items()):
                self.cat_tree.insert("", "end", values=(cat, count, self._format_size(size)))

    def _do_apply(self) -> None:
        """Apply the organization plan (move files)."""
        if not self.plan or self.plan.total_files() == 0:
            messagebox.showwarning("No Plan", "Please generate a preview first.")
            return

        # Confirm action
        total_files = self.plan.total_files()
        total_size = self._format_size(self.plan.total_size())
        if not messagebox.askyesno(
            "Confirm Apply",
            f"This will move {total_files} files ({total_size}).\n\n"
            "This action can be undone using the 'Undo Last' button.\n\n"
            "Are you sure you want to proceed?"
        ):
            return

        try:
            self._update_status("Applying changes...")
            self.progress_bar.grid()
            self.progress_label.grid()
            self.progress_var.set(0)
            self.progress_bar["maximum"] = self.plan.total_files()

            self.preview_btn.config(state="disabled")
            self.apply_btn.config(state="disabled")
            self.undo_btn.config(state="disabled")

            # Execute plan with progress
            self.plan.dry_run = False
            organizer = FileOrganizer(self.config)
            undo_log = self._execute_with_progress_gui(organizer, self.plan)

            self.preview_btn.config(state="normal")
            self.apply_btn.config(state="normal")
            self.undo_btn.config(state="normal")
            self.progress_bar.grid_remove()
            self.progress_label.grid_remove()

            if undo_log.success:
                self._update_status(f"Success: Organized {self.plan.total_files()} files")
                messagebox.showinfo(
                    "Success",
                    f"Successfully organized {self.plan.total_files()} files!\n\n"
                    "You can undo this operation using the 'Undo Last' button."
                )
                # Refresh preview to show empty
                self._do_preview()
            else:
                self._update_status(f"Failed: {undo_log.error}")
                messagebox.showerror("Error", f"Organization failed: {undo_log.error}")

        except Exception as e:
            self.preview_btn.config(state="normal")
            self.apply_btn.config(state="normal")
            self.undo_btn.config(state="normal")
            self.progress_bar.grid_remove()
            self.progress_label.grid_remove()
            self._update_status(f"Error: {e}")
            messagebox.showerror("Error", str(e))

    def _execute_with_progress_gui(
        self, organizer: FileOrganizer, plan: OrganizePlan
    ) -> UndoLog:
        """Execute plan with GUI progress updates."""

        undo_log = UndoLog(plan=plan)

        for i, action in enumerate(plan.actions):
            try:
                src = action.source.resolve()
                dst = action.destination.resolve()

                if not src.exists():
                    raise FileNotFoundError(f"Source file not found: {src}")

                dst.parent.mkdir(parents=True, exist_ok=True)
                os.replace(src, dst)

                # Update progress
                self.progress_var.set(i + 1)
                self.progress_label.config(text=f"Moving {src.name} ({i+1}/{len(plan.actions)})")
                self.root.update_idletasks()

            except Exception as e:
                undo_log.success = False
                undo_log.error = str(e)
                break

        undo_log.completed_at = __import__('time').time()
        organizer._save_undo_log(undo_log)
        return undo_log

    def _do_undo(self) -> None:
        """Undo the last organization operation."""
        organizer = FileOrganizer(self.config)
        undo_log = organizer.get_last_undo_log()

        if not undo_log:
            messagebox.showinfo("Nothing to Undo", "No previous organization to undo.")
            return

        # Show what will be undone
        file_count = undo_log.plan.total_files()
        size_str = self._format_size(undo_log.plan.total_size())

        if not messagebox.askyesno(
            "Confirm Undo",
            f"This will revert the last organization:\n\n"
            f"Mode: {undo_log.plan.mode.value}\n"
            f"Files: {file_count}\n"
            f"Size: {size_str}\n\n"
            "Are you sure you want to undo?"
        ):
            return

        try:
            self._update_status("Undoing...")
            self.progress_bar.grid()
            self.progress_label.grid()
            self.progress_var.set(0)
            self.progress_bar["maximum"] = file_count

            self.preview_btn.config(state="disabled")
            self.apply_btn.config(state="disabled")
            self.undo_btn.config(state="disabled")

            # Execute undo
            for i, action in enumerate(reversed(undo_log.plan.actions)):
                try:
                    src = action.source.resolve()
                    dst = action.destination.resolve()
                    if dst.exists():
                        src.parent.mkdir(parents=True, exist_ok=True)
                        os.replace(dst, src)
                except Exception as e:
                    undo_log.success = False
                    undo_log.error = f"Undo failed: {e}"
                    break

                self.progress_var.set(i + 1)
                current = i + 1
                self.progress_label.config(
                    text=f"Restoring {action.source.name} ({current}/{file_count})"
                )
                self.root.update_idletasks()

            # Clean up
            if undo_log.success:
                from .config import get_undo_log_path
                log_path = get_undo_log_path()
                log_path.unlink(missing_ok=True)
                organizer._cleanup_empty_dirs(undo_log.plan.source_dir)

            self.preview_btn.config(state="normal")
            self.apply_btn.config(state="normal")
            self.undo_btn.config(state="normal")
            self.progress_bar.grid_remove()
            self.progress_label.grid_remove()

            if undo_log.success:
                self._update_status("Undo successful")
                messagebox.showinfo("Success", "Successfully undone!")
                # Refresh preview
                if self.current_path:
                    self._do_preview()
            else:
                self._update_status(f"Undo failed: {undo_log.error}")
                messagebox.showerror("Error", f"Undo failed: {undo_log.error}")

        except Exception as e:
            self.preview_btn.config(state="normal")
            self.apply_btn.config(state="normal")
            self.undo_btn.config(state="normal")
            self.progress_bar.grid_remove()
            self.progress_label.grid_remove()
            self._update_status(f"Error: {e}")
            messagebox.showerror("Error", str(e))


def main() -> None:
    """Entry point for the GUI application."""
    root = tk.Tk()
    FileOrganizerGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()
