# File: cpp_python_debug/gui/config_window.py
# Provides a Toplevel window for configuring application settings.

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import logging
from typing import (
    TYPE_CHECKING,
    Optional,
    Callable,
    List,
    Tuple,
    Any,
)  # Aggiunto Tuple, Any
import os
import time  # Aggiunto per _backup_corrupted_settings_file

# MODIFICA: Import assoluti per i moduli del pacchetto
if TYPE_CHECKING:
    from cpp_python_debug.core.config_manager import AppSettings
    from cpp_python_debug.gui.main_window import (
        GDBGui,
    )  # Usato solo per type hinting del parent

logger = logging.getLogger(__name__)


class ConfigWindow(tk.Toplevel):
    """
    A Toplevel window for viewing and editing application settings.
    """

    def __init__(
        self, parent: "GDBGui", app_settings: "AppSettings"
    ):  # Corretto type hint per parent
        super().__init__(parent)
        self.parent_window = parent  # Mantenuto per reference, anche se 'parent' è già il Toplevel parent
        self.app_settings = app_settings

        self.title("Application Configuration")
        # Recupera il default dalla definizione in AppSettings se la chiave non esiste nel file JSON
        default_gui_settings = self.app_settings._get_default_settings().get("gui", {})
        default_geometry = self.app_settings.get_setting(
            "gui",
            "config_window_geometry",
            default_gui_settings.get(
                "config_window_geometry", "700x620"
            ),  # Fallback finale
        )
        self.geometry(default_geometry)

        self.transient(parent)
        self.grab_set()

        # StringVars & BooleanVars for editable settings
        self.gdb_exe_path_var = tk.StringVar()
        self.gdb_dumper_script_path_var = tk.StringVar()

        self.timeout_gdb_start_var = tk.StringVar()
        self.timeout_gdb_command_var = tk.StringVar()
        self.timeout_program_run_var = tk.StringVar()
        self.timeout_dump_variable_var = tk.StringVar()
        self.timeout_kill_program_var = tk.StringVar()
        self.timeout_gdb_quit_var = tk.StringVar()

        self.dumper_max_array_elements_var = tk.StringVar()
        self.dumper_max_recursion_depth_var = tk.StringVar()
        self.dumper_max_string_length_var = tk.StringVar()

        self.dumper_dump_diagnostic_json_to_file_var = tk.BooleanVar()
        self.dumper_diagnostic_json_output_dir_var = tk.StringVar()

        self._load_current_settings_to_vars()
        self._create_widgets()

        self.protocol("WM_DELETE_WINDOW", self._on_closing_button)
        # self.wait_window() # Rimosso da qui, la finestra chiamante (main_window) farà wait_window

    def _load_current_settings_to_vars(self):
        logger.debug("Loading current settings into ConfigWindow variables.")
        # Ottieni i valori di default una volta per evitare chiamate multiple a _get_default_settings()
        default_settings = self.app_settings._get_default_settings()
        default_general = default_settings.get("general", {})
        default_timeouts = default_settings.get("timeouts", {})
        default_dumper_opts = default_settings.get("dumper_options", {})

        self.gdb_exe_path_var.set(
            self.app_settings.get_setting(
                "general",
                "gdb_executable_path",
                default_general.get("gdb_executable_path"),
            )
        )
        self.gdb_dumper_script_path_var.set(
            self.app_settings.get_setting(
                "general",
                "gdb_dumper_script_path",
                default_general.get("gdb_dumper_script_path"),
            )
        )

        self.timeout_gdb_start_var.set(
            str(
                self.app_settings.get_setting(
                    "timeouts", "gdb_start", default_timeouts.get("gdb_start")
                )
            )
        )
        self.timeout_gdb_command_var.set(
            str(
                self.app_settings.get_setting(
                    "timeouts", "gdb_command", default_timeouts.get("gdb_command")
                )
            )
        )
        self.timeout_program_run_var.set(
            str(
                self.app_settings.get_setting(
                    "timeouts",
                    "program_run_continue",
                    default_timeouts.get("program_run_continue"),
                )
            )
        )
        self.timeout_dump_variable_var.set(
            str(
                self.app_settings.get_setting(
                    "timeouts", "dump_variable", default_timeouts.get("dump_variable")
                )
            )
        )
        self.timeout_kill_program_var.set(
            str(
                self.app_settings.get_setting(
                    "timeouts", "kill_program", default_timeouts.get("kill_program")
                )
            )
        )
        self.timeout_gdb_quit_var.set(
            str(
                self.app_settings.get_setting(
                    "timeouts", "gdb_quit", default_timeouts.get("gdb_quit")
                )
            )
        )

        self.dumper_max_array_elements_var.set(
            str(
                self.app_settings.get_setting(
                    "dumper_options",
                    "max_array_elements",
                    default_dumper_opts.get("max_array_elements"),
                )
            )
        )
        self.dumper_max_recursion_depth_var.set(
            str(
                self.app_settings.get_setting(
                    "dumper_options",
                    "max_recursion_depth",
                    default_dumper_opts.get("max_recursion_depth"),
                )
            )
        )
        self.dumper_max_string_length_var.set(
            str(
                self.app_settings.get_setting(
                    "dumper_options",
                    "max_string_length",
                    default_dumper_opts.get("max_string_length"),
                )
            )
        )

        self.dumper_dump_diagnostic_json_to_file_var.set(
            self.app_settings.get_setting(
                "dumper_options",
                "dump_diagnostic_json_to_file",
                default_dumper_opts.get("dump_diagnostic_json_to_file"),
            )
        )
        self.dumper_diagnostic_json_output_dir_var.set(
            self.app_settings.get_setting(
                "dumper_options",
                "diagnostic_json_output_dir",
                default_dumper_opts.get("diagnostic_json_output_dir"),
            )
        )

    def _create_widgets(self):
        # (Implementazione come prima, ma con `parent=self` per `filedialog`)
        main_frame = ttk.Frame(self, padding="10")
        main_frame.pack(expand=True, fill=tk.BOTH)

        notebook = ttk.Notebook(main_frame)
        notebook.pack(expand=True, fill=tk.BOTH, pady=5)

        paths_frame = ttk.Frame(notebook, padding="10")
        notebook.add(paths_frame, text="Paths & Directories")
        self._create_paths_widgets_revised(paths_frame)

        timeouts_frame = ttk.Frame(notebook, padding="10")
        notebook.add(timeouts_frame, text="Timeouts (s)")
        self._create_timeouts_widgets(timeouts_frame)

        dumper_frame = ttk.Frame(notebook, padding="10")
        notebook.add(dumper_frame, text="Dumper Options")
        self._create_dumper_widgets_revised(dumper_frame)

        button_frame = ttk.Frame(main_frame, padding=(0, 10, 0, 0))
        button_frame.pack(fill=tk.X, side=tk.BOTTOM)
        ttk.Button(
            button_frame, text="Save", command=self._apply_and_save_settings
        ).pack(side=tk.RIGHT, padx=5)
        ttk.Button(button_frame, text="Cancel", command=self._cancel).pack(
            side=tk.RIGHT
        )

    def _create_path_input_group(
        self,
        parent: ttk.Frame,
        row_start: int,
        label_text: str,
        textvariable: tk.StringVar,
        help_text: str,
        browse_command: Optional[
            Callable[[tk.StringVar, str, Optional[List[Tuple[str, Any]]]], None]
        ] = None,  # Corretto tipo Callable
        browse_filetypes: Optional[List[Tuple[str, Any]]] = None,
    ) -> int:
        # (Implementazione come prima)
        ttk.Label(parent, text=label_text).grid(
            row=row_start, column=0, columnspan=2, sticky=tk.W, padx=5, pady=(5, 0)
        )
        row_start += 1

        entry_frame = ttk.Frame(parent)
        entry_frame.grid(
            row=row_start, column=0, columnspan=2, sticky="ew", padx=5, pady=(0, 2)
        )
        entry_frame.columnconfigure(0, weight=1)

        entry = ttk.Entry(entry_frame, textvariable=textvariable)
        entry.grid(row=0, column=0, sticky="ew")

        if browse_command:
            browse_btn = ttk.Button(
                entry_frame,
                text="Browse...",
                command=lambda tv=textvariable, tl=label_text, ft=browse_filetypes: browse_command(
                    tv, tl, ft
                ),
            )
            browse_btn.grid(row=0, column=1, padx=(5, 0))

        row_start += 1
        ttk.Label(
            parent, text=help_text, foreground="gray", font=("TkDefaultFont", 8)
        ).grid(row=row_start, column=0, columnspan=2, sticky=tk.W, padx=7, pady=(0, 10))
        row_start += 1
        return row_start

    def _create_paths_widgets_revised(self, parent_frame: ttk.Frame):
        # (Implementazione come prima)
        paths_config_frame = ttk.LabelFrame(
            parent_frame, text="Core Paths", padding="10"
        )
        paths_config_frame.pack(expand=True, fill=tk.BOTH, pady=5)
        paths_config_frame.columnconfigure(0, weight=1)

        current_row = 0
        current_row = self._create_path_input_group(
            paths_config_frame,
            current_row,
            "GDB Executable Path:",
            self.gdb_exe_path_var,
            "(Full path to the gdb.exe or gdb command)",
            browse_command=self._browse_file,
            browse_filetypes=[("Executable files", "*.exe gdb"), ("All files", "*.*")],
        )

        current_row = self._create_path_input_group(
            paths_config_frame,
            current_row,
            "GDB Python Dumper Script Path (Optional):",
            self.gdb_dumper_script_path_var,
            "(Full path to the gdb_dumper.py script. Leave empty if not using advanced JSON dump.)",
            browse_command=self._browse_file,
            browse_filetypes=[("Python files", "*.py"), ("All files", "*.*")],
        )
        parent_frame.rowconfigure(current_row, weight=1)

    def _create_timeouts_widgets(self, parent_frame: ttk.Frame):
        # (Implementazione come prima)
        timeouts_config_frame = ttk.LabelFrame(
            parent_frame, text="GDB Operation Timeouts", padding="10"
        )
        timeouts_config_frame.pack(expand=True, fill=tk.X, pady=5)
        timeouts_config_frame.columnconfigure(0, weight=0, pad=5)
        timeouts_config_frame.columnconfigure(1, weight=0, pad=5)
        timeouts_config_frame.columnconfigure(2, weight=1, pad=5)

        timeout_settings = [
            (
                "GDB Start:",
                self.timeout_gdb_start_var,
                "Timeout for GDB process to start.",
            ),
            (
                "GDB Command:",
                self.timeout_gdb_command_var,
                "Default timeout for GDB commands.",
            ),
            (
                "Program Run/Continue:",
                self.timeout_program_run_var,
                "Timeout for 'run' or 'continue'.",
            ),
            (
                "Dump Variable:",
                self.timeout_dump_variable_var,
                "Timeout for 'dump_json' operation.",
            ),
            (
                "Kill Program:",
                self.timeout_kill_program_var,
                "Timeout for GDB to confirm 'kill'.",
            ),
            (
                "GDB Quit:",
                self.timeout_gdb_quit_var,
                "Timeout for GDB to exit via 'quit'.",
            ),
        ]

        for i, (label_text, var, tooltip_text) in enumerate(timeout_settings):
            ttk.Label(timeouts_config_frame, text=label_text).grid(
                row=i, column=0, sticky=tk.W
            )
            spinbox = ttk.Spinbox(
                timeouts_config_frame,
                textvariable=var,
                from_=1,
                to=3600,
                increment=1,
                width=8,
            )
            spinbox.grid(row=i, column=1, sticky=tk.W)
            ttk.Label(
                timeouts_config_frame,
                text=f"({tooltip_text})",
                foreground="gray",
                font=("TkDefaultFont", 8),
            ).grid(row=i, column=2, sticky=tk.W, padx=5)

    def _create_dumper_widgets_revised(self, parent_frame: ttk.Frame):
        # (Implementazione come prima)
        dumper_config_frame = ttk.LabelFrame(
            parent_frame, text="GDB Python Dumper Script Options", padding="10"
        )
        dumper_config_frame.pack(expand=True, fill=tk.BOTH, pady=5)
        dumper_config_frame.columnconfigure(0, weight=1)

        numeric_options_frame = ttk.Frame(dumper_config_frame)
        numeric_options_frame.grid(row=0, column=0, sticky="ew", pady=(0, 10))
        numeric_options_frame.columnconfigure(0, weight=0, pad=5)
        numeric_options_frame.columnconfigure(1, weight=0, pad=5)
        numeric_options_frame.columnconfigure(2, weight=1, pad=5)

        dumper_settings = [
            (
                "Max Array Elements:",
                self.dumper_max_array_elements_var,
                "Max elements from arrays/containers.",
            ),
            (
                "Max Recursion Depth:",
                self.dumper_max_recursion_depth_var,
                "Max depth for recursive structures.",
            ),
            (
                "Max String Length:",
                self.dumper_max_string_length_var,
                "Max characters from a string.",
            ),
        ]
        for i, (label_text, var, tooltip_text) in enumerate(dumper_settings):
            ttk.Label(numeric_options_frame, text=label_text).grid(
                row=i, column=0, sticky=tk.W
            )
            spinbox = ttk.Spinbox(
                numeric_options_frame,
                textvariable=var,
                from_=0,
                to=100000,
                increment=1,
                width=8,
            )
            spinbox.grid(row=i, column=1, sticky=tk.W)
            ttk.Label(
                numeric_options_frame,
                text=f"({tooltip_text})",
                foreground="gray",
                font=("TkDefaultFont", 8),
            ).grid(row=i, column=2, sticky=tk.W, padx=5)

        current_row = 1
        ttk.Checkbutton(
            dumper_config_frame,
            text="Enable Diagnostic JSON Dump to File",
            variable=self.dumper_dump_diagnostic_json_to_file_var,
        ).grid(row=current_row, column=0, sticky=tk.W, padx=5, pady=(10, 2))
        current_row += 1
        ttk.Label(
            dumper_config_frame,
            text="(For debugging the dumper script. Saves raw JSON from GDB dumper.)",
            foreground="gray",
            font=("TkDefaultFont", 8),
        ).grid(row=current_row, column=0, sticky=tk.W, padx=7, pady=(0, 10))
        current_row += 1

        current_row = self._create_path_input_group(
            dumper_config_frame,
            current_row,
            "Diagnostic JSON Output Directory:",
            self.dumper_diagnostic_json_output_dir_var,
            "(Folder for diagnostic dumps. Leave empty for default in app config dir.)",  # Modificato help text
            browse_command=self._browse_directory,  # Ora usa la sua funzione dedicata
        )
        parent_frame.rowconfigure(current_row, weight=1)

    def _browse_file(
        self,
        target_var: tk.StringVar,
        title: str,
        filetypes: Optional[List[Tuple[str, Any]]] = None,
    ):
        # (Implementazione come prima)
        current_path = target_var.get()
        initial_dir = (
            os.path.dirname(current_path)
            if current_path and os.path.isdir(os.path.dirname(current_path))
            else None
        )  # Corretto isdir
        path = filedialog.askopenfilename(
            title=title,
            filetypes=filetypes or [("All files", "*.*")],
            initialdir=initial_dir,
            parent=self,
        )
        if path:
            target_var.set(path)

    def _browse_directory(
        self,
        target_var: tk.StringVar,
        title: str,
        _unused_filetypes: Optional[List[Tuple[str, Any]]] = None,
    ):  # Aggiunto _unused per coerenza
        # (Implementazione come prima)
        current_path = target_var.get()
        initial_dir = (
            current_path if current_path and os.path.isdir(current_path) else None
        )
        path = filedialog.askdirectory(title=title, initialdir=initial_dir, parent=self)
        if path:
            target_var.set(path)

    def _validate_settings(self) -> bool:
        # (Implementazione come prima, ma con time importato per _backup_corrupted_settings_file se fosse qui)
        validations = [
            (self.timeout_gdb_start_var, "GDB Start Timeout", 1, 3600),
            (self.timeout_gdb_command_var, "GDB Command Timeout", 1, 3600),
            (self.timeout_program_run_var, "Program Run/Continue Timeout", 1, 7200),
            (self.timeout_dump_variable_var, "Dump Variable Timeout", 1, 3600),
            (self.timeout_kill_program_var, "Kill Program Timeout", 1, 300),
            (self.timeout_gdb_quit_var, "GDB Quit Timeout", 1, 300),
            (self.dumper_max_array_elements_var, "Max Array Elements", 0, 100000),
            (self.dumper_max_recursion_depth_var, "Max Recursion Depth", 1, 100),
            (self.dumper_max_string_length_var, "Max String Length", 0, 1048576),
        ]
        errors = []
        for var, name, min_val, max_val in validations:
            try:
                value = int(var.get())
                if not (min_val <= value <= max_val):
                    errors.append(
                        f"{name} must be between {min_val}-{max_val}. Got: {value}"
                    )
            except ValueError:
                errors.append(f"{name} must be a valid integer. Got: '{var.get()}'")

        if not self.gdb_exe_path_var.get():
            errors.append("GDB Executable path cannot be empty.")

        if self.dumper_dump_diagnostic_json_to_file_var.get():
            diag_output_dir = self.dumper_diagnostic_json_output_dir_var.get().strip()
            if (
                diag_output_dir
            ):  # Solo se specificato, altrimenti usa default da AppSettings
                if not os.path.isdir(diag_output_dir):
                    try:
                        os.makedirs(diag_output_dir, exist_ok=True)
                        logger.info(
                            f"Created diagnostic output directory during validation: {diag_output_dir}"
                        )
                    except OSError as e_create_diag:
                        errors.append(
                            f"Diagnostic JSON Output Dir '{diag_output_dir}' is not valid and could not be created: {e_create_diag}"
                        )

        if errors:
            messagebox.showerror("Validation Error", "\n".join(errors), parent=self)
            return False
        return True

    def _apply_and_save_settings(self):
        # (Implementazione come prima)
        logger.info("Attempting to apply and save settings from ConfigWindow.")
        if not self._validate_settings():
            logger.warning("Settings validation failed.")
            return

        try:
            self.app_settings.set_setting(
                "general", "gdb_executable_path", self.gdb_exe_path_var.get()
            )
            self.app_settings.set_setting(
                "general",
                "gdb_dumper_script_path",
                self.gdb_dumper_script_path_var.get(),
            )

            self.app_settings.set_setting(
                "timeouts", "gdb_start", int(self.timeout_gdb_start_var.get())
            )
            self.app_settings.set_setting(
                "timeouts", "gdb_command", int(self.timeout_gdb_command_var.get())
            )
            self.app_settings.set_setting(
                "timeouts",
                "program_run_continue",
                int(self.timeout_program_run_var.get()),
            )
            self.app_settings.set_setting(
                "timeouts", "dump_variable", int(self.timeout_dump_variable_var.get())
            )
            self.app_settings.set_setting(
                "timeouts", "kill_program", int(self.timeout_kill_program_var.get())
            )
            self.app_settings.set_setting(
                "timeouts", "gdb_quit", int(self.timeout_gdb_quit_var.get())
            )

            self.app_settings.set_setting(
                "dumper_options",
                "max_array_elements",
                int(self.dumper_max_array_elements_var.get()),
            )
            self.app_settings.set_setting(
                "dumper_options",
                "max_recursion_depth",
                int(self.dumper_max_recursion_depth_var.get()),
            )
            self.app_settings.set_setting(
                "dumper_options",
                "max_string_length",
                int(self.dumper_max_string_length_var.get()),
            )

            self.app_settings.set_setting(
                "dumper_options",
                "dump_diagnostic_json_to_file",
                self.dumper_dump_diagnostic_json_to_file_var.get(),
            )
            self.app_settings.set_setting(
                "dumper_options",
                "diagnostic_json_output_dir",
                self.dumper_diagnostic_json_output_dir_var.get().strip(),
            )

            self.app_settings.set_setting(
                "gui", "config_window_geometry", self.geometry()
            )

            if self.app_settings.save_settings():
                logger.info("Settings applied and saved successfully.")
                messagebox.showinfo(
                    "Settings Saved", "Configuration has been saved.", parent=self
                )
                self.destroy()
            else:
                logger.error("Failed to save settings to file.")
                messagebox.showerror(
                    "Save Error",
                    "Could not save settings. Check application logs.",
                    parent=self,
                )
        except Exception as e:
            logger.error(f"Unexpected error applying settings: {e}", exc_info=True)
            messagebox.showerror(
                "Internal Error",
                f"An unexpected error occurred while applying settings: {e}",
                parent=self,
            )

    def _cancel(self):
        # (Implementazione come prima)
        logger.info("Configuration changes cancelled by user.")
        self.destroy()

    def _on_closing_button(self):
        # (Implementazione come prima)
        logger.debug("ConfigWindow close button (X) pressed.")
        self._cancel()  # Tratta come un cancel
