# target_simulator/gui/main_view.py

"""
Main view of the application, containing the primary window and widgets.
"""
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
from queue import Queue, Empty
from typing import Optional, Dict, Any, List

# Use absolute imports for robustness and clarity
from target_simulator.gui.ppi_display import PPIDisplay
from target_simulator.gui.connection_settings_window import ConnectionSettingsWindow
from target_simulator.gui.radar_config_window import RadarConfigWindow
from target_simulator.gui.scenario_controls_frame import ScenarioControlsFrame
from target_simulator.gui.target_list_frame import TargetListFrame

from target_simulator.core.communicator_interface import CommunicatorInterface
from target_simulator.core.serial_communicator import SerialCommunicator
from target_simulator.core.tftp_communicator import TFTPCommunicator
from target_simulator.core.simulation_engine import SimulationEngine
from target_simulator.core.models import Scenario, Target

from target_simulator.utils.logger import get_logger, shutdown_logging_system
from target_simulator.utils.config_manager import ConfigManager
from target_simulator.gui.sfp_debug_window import SfpDebugWindow


GUI_QUEUE_POLL_INTERVAL_MS = 100


class MainView(tk.Tk):
    """The main application window."""

    def __init__(self):
        super().__init__()
        self.logger = get_logger(__name__)
        self.config_manager = ConfigManager()

        # --- Load Settings ---
        settings = self.config_manager.get_general_settings()
        self.scan_limit = settings.get("scan_limit", 60)
        self.max_range = settings.get("max_range", 100)
        self.connection_config = self.config_manager.get_connection_settings()

        # --- Core Logic Handlers ---
        self.target_communicator: Optional[CommunicatorInterface] = None
        self.lru_communicator: Optional[CommunicatorInterface] = None
        self.scenario = Scenario()
        self.current_scenario_name: Optional[str] = None
        self.sfp_debug_window: Optional[SfpDebugWindow] = None

        # --- Simulation Engine ---
        self.simulation_engine: Optional[SimulationEngine] = None
        self.gui_update_queue = Queue()
        self.is_simulation_running = tk.BooleanVar(value=False)
        self.time_multiplier = 1.0
        self.update_time = tk.DoubleVar(value=1.0)

        # --- Window and UI Setup ---
        self.title("Radar Target Simulator")
        self.geometry(settings.get("geometry", "1200x1024"))
        self.minsize(1024, 768)

        self._create_menubar()
        self._create_main_layout()
        self._create_statusbar()

        # --- Post-UI Initialization ---
        self._initialize_communicators()
        self._load_scenarios_into_ui()
        last_scenario = settings.get("last_selected_scenario")
        if last_scenario and last_scenario in self.config_manager.get_scenario_names():
            self._on_load_scenario(last_scenario)

        self._update_window_title()
        self.protocol("WM_DELETE_WINDOW", self._on_closing)
        self.logger.info("MainView initialized successfully.")

    def _create_main_layout(self):
        v_pane = ttk.PanedWindow(self, orient=tk.VERTICAL)
        v_pane.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.h_pane = ttk.PanedWindow(v_pane, orient=tk.HORIZONTAL)
        v_pane.add(self.h_pane, weight=4)

        # --- Right Pane (PPI) ---
        self.ppi_widget = PPIDisplay(
            self.h_pane, max_range_nm=self.max_range, scan_limit_deg=self.scan_limit
        )
        self.h_pane.add(self.ppi_widget, weight=2)

        # Add Connect button to the PPI's own control frame for better layout
        if hasattr(self.ppi_widget, "controls_frame"):
            connect_btn = ttk.Button(
                self.ppi_widget.controls_frame,
                text="Connect",
                command=self._on_connect_button,
            )
            connect_btn.pack(side=tk.RIGHT, padx=10)

        # --- Left Pane ---
        left_pane_container = ttk.Frame(self.h_pane)
        self.h_pane.add(left_pane_container, weight=1)

        left_notebook = ttk.Notebook(left_pane_container)
        left_notebook.pack(fill=tk.BOTH, expand=True)

        # --- TAB 1: SCENARIO CONFIG ---
        scenario_tab = ttk.Frame(left_notebook)
        left_notebook.add(scenario_tab, text="Scenario Config")

        self.scenario_controls = ScenarioControlsFrame(
            scenario_tab,
            main_view=self,
            load_scenario_command=self._on_load_scenario,
            save_as_command=self._on_save_scenario_as,
            delete_command=self._on_delete_scenario,
            new_scenario_command=self._on_new_scenario,
        )
        self.scenario_controls.pack(fill=tk.X, expand=False, padx=5, pady=(5, 5))

        self.target_list = TargetListFrame(
            scenario_tab, targets_changed_callback=self._on_targets_changed
        )
        self.target_list.pack(fill=tk.BOTH, expand=True, padx=5)

        # --- TAB 2: SIMULATION ---
        simulation_tab = ttk.Frame(left_notebook)
        left_notebook.add(simulation_tab, text="Simulation")

        sim_scenario_frame = ttk.LabelFrame(simulation_tab, text="Scenario Control")
        sim_scenario_frame.pack(fill=tk.X, padx=5, pady=5, anchor="n")

        ttk.Label(sim_scenario_frame, text="Scenario:").pack(
            side=tk.LEFT, padx=(5, 5), pady=5
        )
        self.sim_scenario_combobox = ttk.Combobox(
            sim_scenario_frame,
            textvariable=self.scenario_controls.current_scenario,  # Share the variable
            state="readonly",
        )
        self.sim_scenario_combobox.pack(side=tk.LEFT, expand=True, fill=tk.X, pady=5)
        self.sim_scenario_combobox.bind(
            "<<ComboboxSelected>>",
            lambda event: self._on_load_scenario(self.sim_scenario_combobox.get()),
        )

        engine_frame = ttk.LabelFrame(simulation_tab, text="Live Simulation Engine")
        engine_frame.pack(fill=tk.X, padx=5, pady=10, anchor="n")

        self.start_button = ttk.Button(
            engine_frame, text="Start Live", command=self._on_start_simulation
        )
        self.start_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.stop_button = ttk.Button(
            engine_frame,
            text="Stop Live",
            command=self._on_stop_simulation,
            state=tk.DISABLED,
        )
        self.stop_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.reset_button = ttk.Button(
            engine_frame, text="Reset State", command=self._on_reset_simulation
        )
        self.reset_button.pack(side=tk.RIGHT, padx=5, pady=5)

        ttk.Label(engine_frame, text="Speed:").pack(side=tk.LEFT, padx=(10, 2), pady=5)
        self.time_multiplier_var = tk.StringVar(value="1x")
        self.multiplier_combo = ttk.Combobox(
            engine_frame,
            textvariable=self.time_multiplier_var,
            values=["1x", "2x", "4x", "10x", "20x"],
            state="readonly",
            width=4,
        )
        self.multiplier_combo.pack(side=tk.LEFT, padx=(0, 5), pady=5)
        self.multiplier_combo.bind(
            "<<ComboboxSelected>>", self._on_time_multiplier_changed
        )

        ttk.Label(engine_frame, text="Update Time (s):").pack(
            side=tk.LEFT, padx=(10, 2), pady=5
        )
        self.update_time_entry = ttk.Entry(
            engine_frame, textvariable=self.update_time, width=5
        )
        self.update_time_entry.pack(side=tk.LEFT, padx=(0, 5), pady=5)

        # --- TAB 3: LRU SIMULATION ---
        lru_tab = ttk.Frame(left_notebook)
        left_notebook.add(lru_tab, text="LRU Simulation")

        cooling_frame = ttk.LabelFrame(lru_tab, text="Cooling Unit Status")
        cooling_frame.pack(fill=tk.X, padx=5, pady=5, anchor="n")
        ttk.Label(cooling_frame, text="Status:").pack(side=tk.LEFT, padx=5, pady=5)
        self.cooling_status_var = tk.StringVar(value="OK")
        cooling_combo = ttk.Combobox(
            cooling_frame,
            textvariable=self.cooling_status_var,
            values=["OK", "OVERHEATING", "FAULT"],
            state="readonly",
        )
        cooling_combo.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5, pady=5)

        power_frame = ttk.LabelFrame(lru_tab, text="Power Supply Unit Status")
        power_frame.pack(fill=tk.X, padx=5, pady=5, anchor="n")
        ttk.Label(power_frame, text="Status:").pack(side=tk.LEFT, padx=5, pady=5)
        self.power_status_var = tk.StringVar(value="OK")
        power_combo = ttk.Combobox(
            power_frame,
            textvariable=self.power_status_var,
            values=["OK", "LOW_VOLTAGE", "FAULT"],
            state="readonly",
        )
        power_combo.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5, pady=5)

        lru_action_frame = ttk.Frame(lru_tab)
        lru_action_frame.pack(fill=tk.X, padx=5, pady=10, anchor="n")
        send_lru_button = ttk.Button(
            lru_action_frame, text="Send LRU Status", command=self._on_send_lru_status
        )
        send_lru_button.pack(side=tk.RIGHT)

        # --- Bottom Pane (Logs) ---
        log_frame_container = ttk.LabelFrame(v_pane, text="Logs")
        v_pane.add(log_frame_container, weight=1)
        self.log_text_widget = scrolledtext.ScrolledText(
            log_frame_container, state=tk.DISABLED, wrap=tk.WORD, font=("Consolas", 9)
        )
        self.log_text_widget.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

    def _create_menubar(self):
        menubar = tk.Menu(self)
        self.config(menu=menubar)
        settings_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Settings", menu=settings_menu)
        settings_menu.add_command(label="Connection...", command=self._open_settings)
        settings_menu.add_command(
            label="Radar Config...", command=self._open_radar_config
        )

        debug_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="Debug", menu=debug_menu)
        debug_menu.add_command(
            label="SFP Packet Inspector...", command=self._open_sfp_debug_window
        )

    def _create_statusbar(self):
        status_bar = ttk.Frame(self, relief=tk.SUNKEN)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        ttk.Label(status_bar, text="Target:").pack(side=tk.LEFT, padx=(5, 2))
        self.target_status_canvas = tk.Canvas(
            status_bar, width=16, height=16, highlightthickness=0
        )
        self.target_status_canvas.pack(side=tk.LEFT, padx=(0, 10))
        self._draw_status_indicator(self.target_status_canvas, "#e74c3c")
        ttk.Label(status_bar, text="LRU:").pack(side=tk.LEFT, padx=(5, 2))
        self.lru_status_canvas = tk.Canvas(
            status_bar, width=16, height=16, highlightthickness=0
        )
        self.lru_status_canvas.pack(side=tk.LEFT, padx=(0, 10))
        self._draw_status_indicator(self.lru_status_canvas, "#e74c3c")
        self.status_var = tk.StringVar(value="Ready")
        ttk.Label(status_bar, textvariable=self.status_var, anchor=tk.W).pack(
            side=tk.LEFT, fill=tk.X, expand=True, padx=5
        )

    def _draw_status_indicator(self, canvas, color):
        canvas.delete("all")
        canvas.create_oval(2, 2, 14, 14, fill=color, outline="black")

    def _update_window_title(self):
        """Updates the window title based on the current scenario."""
        base_title = "Radar Target Simulator"
        if self.current_scenario_name:
            self.title(f"{base_title} - {self.current_scenario_name}")
        else:
            self.title(base_title)

    def _update_communicator_status(self, comm_name: str, is_connected: bool):
        canvas = (
            self.target_status_canvas
            if comm_name == "Target"
            else self.lru_status_canvas
        )
        color = "#2ecc40" if is_connected else "#e74c3c"
        self._draw_status_indicator(canvas, color)

    def _initialize_communicators(self):
        # Disconnect any existing connections
        if self.target_communicator and self.target_communicator.is_open:
            self.target_communicator.disconnect()
        if self.lru_communicator and self.lru_communicator.is_open:
            self.lru_communicator.disconnect()

        target_cfg = self.connection_config.get("target", {})
        lru_cfg = self.connection_config.get("lru", {})

        # Initialize Target Communicator
        self.target_communicator, target_connected = self._setup_communicator(
            target_cfg, "Target"
        )
        self._update_communicator_status("Target", target_connected)

        # Initialize LRU Communicator
        self.lru_communicator, lru_connected = self._setup_communicator(lru_cfg, "LRU")
        self._update_communicator_status("LRU", lru_connected)

    def _setup_communicator(
        self, config: dict, name: str
    ) -> (Optional[CommunicatorInterface], bool):
        comm_type = config.get("type")
        self.logger.info(f"Initializing {name} communicator of type: {comm_type}")

        communicator = None
        config_data = None

        if comm_type == "serial":
            communicator = SerialCommunicator()
            config_data = config.get("serial", {})
        elif comm_type == "tftp":
            communicator = TFTPCommunicator()
            config_data = config.get("tftp", {})

        if communicator and config_data:
            if communicator.connect(config_data):
                return communicator, True

        self.logger.warning(f"Failed to initialize or connect {name} communicator.")
        return None, False

    def update_connection_settings(self, new_config: Dict[str, Any]):
        self.logger.info(f"Updating connection settings: {new_config}")
        self.connection_config = new_config
        self.config_manager.save_connection_settings(new_config)
        self._initialize_communicators()

    def _open_settings(self):
        self.logger.info("Opening connection settings window.")
        ConnectionSettingsWindow(self, self.config_manager, self.connection_config)

    def _on_connect_button(self):
        self.logger.info("Connection requested by user.")
        self._initialize_communicators()

    def _on_start_simulation(self):
        if self.is_simulation_running.get():
            self.logger.info("Simulation is already running.")
            return
        if not self.target_communicator or not self.target_communicator.is_open:
            messagebox.showerror(
                "Not Connected",
                "Target communicator is not connected. Please check settings and connect.",
            )
            return
        if not self.scenario or not self.scenario.get_all_targets():
            messagebox.showinfo(
                "Empty Scenario", "Cannot start simulation with an empty scenario."
            )
            return

        try:
            update_interval = self.update_time.get()
            if update_interval <= 0:
                messagebox.showwarning(
                    "Invalid Input", "Update time must be a positive number."
                )
                return
        except tk.TclError:
            messagebox.showwarning(
                "Invalid Input", "Update time must be a valid number."
            )
            return

        # --- MODIFICATION START ---
        # 1. Atomically send the initial state of the entire scenario first.
        self.logger.info(
            "Sending initial scenario state before starting live updates..."
        )
        if not self.target_communicator.send_scenario(self.scenario):
            self.logger.error(
                "Failed to send initial scenario state. Aborting live simulation start."
            )
            messagebox.showerror(
                "Send Error",
                "Failed to send the initial scenario configuration. Cannot start live simulation.",
            )
            return
        self.logger.info("Initial scenario state sent successfully.")
        # --- MODIFICATION END ---

        self.logger.info("Starting live simulation...")
        self.is_simulation_running.set(True)
        self._update_button_states()

        self.scenario.reset_simulation()
        self.simulation_engine = SimulationEngine(
            self.target_communicator, self.gui_update_queue
        )
        self.simulation_engine.set_time_multiplier(self.time_multiplier)
        self.simulation_engine.set_update_interval(update_interval)
        self.simulation_engine.load_scenario(self.scenario)
        self.simulation_engine.start()

        self.after(GUI_QUEUE_POLL_INTERVAL_MS, self._process_gui_queue)

    def _on_stop_simulation(self):
        if not self.is_simulation_running.get() or not self.simulation_engine:
            return

        self.logger.info("Stopping live simulation...")
        self.simulation_engine.stop()
        self.simulation_engine = None
        self.is_simulation_running.set(False)
        self._update_button_states()

    def _on_reset_simulation(self):
        self.logger.info("Resetting scenario to initial state.")
        if self.is_simulation_running.get():
            self._on_stop_simulation()

        self.scenario.reset_simulation()
        self._update_all_views()

    def _process_gui_queue(self):
        try:
            while not self.gui_update_queue.empty():
                update = self.gui_update_queue.get_nowait()
                if update == "SIMULATION_FINISHED":
                    self.logger.info(
                        "Simulazione terminata: aggiorno pulsanti per nuovo avvio."
                    )
                    self._on_stop_simulation()
                else:
                    updated_targets: List[Target] = update
                    self._update_all_views(updated_targets)
        finally:
            if self.is_simulation_running.get():
                self.after(GUI_QUEUE_POLL_INTERVAL_MS, self._process_gui_queue)

    def _update_button_states(self):
        is_running = self.is_simulation_running.get()
        state = tk.DISABLED if is_running else tk.NORMAL

        self.start_button.config(state=tk.DISABLED if is_running else tk.NORMAL)
        self.stop_button.config(state=tk.NORMAL if is_running else tk.DISABLED)
        self.multiplier_combo.config(
            state="readonly" if not is_running else tk.DISABLED
        )

        self.scenario_controls.new_button.config(state=state)
        self.scenario_controls.save_button.config(state=state)
        self.scenario_controls.save_as_button.config(state=state)
        self.scenario_controls.delete_button.config(state=state)
        self.scenario_controls.scenario_combobox.config(
            state="readonly" if not is_running else tk.DISABLED
        )

        self.target_list.add_button.config(state=state)
        self.target_list.remove_button.config(state=state)
        self.target_list.edit_button.config(state=state)
        self.target_list.tree.config(selectmode="browse" if not is_running else "none")

    def _on_time_multiplier_changed(self, event=None):
        """Handles changes to the time multiplier selection."""
        try:
            multiplier_str = self.time_multiplier_var.get().replace("x", "")
            self.time_multiplier = float(multiplier_str)
            if self.simulation_engine and self.simulation_engine.is_running():
                self.simulation_engine.set_time_multiplier(self.time_multiplier)
        except ValueError:
            self.logger.error(
                f"Invalid time multiplier value: {self.time_multiplier_var.get()}"
            )
            self.time_multiplier = 1.0

    def _on_targets_changed(self, targets: List[Target]):
        """Callback executed when the target list is modified by the user."""
        # 1. Update the internal scenario object
        self.scenario.targets = {t.target_id: t for t in targets}

        # 2. Update the PPI display with the latest target list
        self.ppi_widget.update_targets(targets)

        # 3. Automatically save the changes to the current scenario file
        if self.current_scenario_name:
            self.logger.info(
                f"Targets changed for scenario '{self.current_scenario_name}'. Saving changes."
            )
            self.config_manager.save_scenario(
                self.current_scenario_name, self.scenario.to_dict()
            )
        else:
            self.logger.warning(
                "Targets changed, but no scenario is currently loaded. Changes are not saved."
            )

    def _update_all_views(self, targets_to_display: Optional[List[Target]] = None):
        self._update_window_title()

        if targets_to_display is None:
            targets_to_display = self.scenario.get_all_targets()

        self.target_list.update_target_list(targets_to_display)
        self.ppi_widget.update_targets(targets_to_display)

    def _load_scenarios_into_ui(self):
        scenario_names = self.config_manager.get_scenario_names()
        self.scenario_controls.update_scenario_list(
            scenario_names, self.current_scenario_name
        )
        self.sim_scenario_combobox["values"] = scenario_names

    def _on_load_scenario(self, scenario_name: str):
        if self.is_simulation_running.get():
            self._on_stop_simulation()
        self.logger.info(f"Loading scenario: {scenario_name}")
        scenario_data = self.config_manager.get_scenario(scenario_name)
        if scenario_data:
            try:
                self.scenario = Scenario.from_dict(scenario_data)
                self.current_scenario_name = scenario_name
                # Update target list UI with loaded scenario's targets
                self.target_list.update_target_list(self.scenario.get_all_targets())
                self._update_all_views()
                self.sim_scenario_combobox.set(scenario_name)
            except Exception as e:
                self.logger.error(
                    f"Failed to parse scenario data for '{scenario_name}': {e}",
                    exc_info=True,
                )
                messagebox.showerror(
                    "Load Error", f"Could not load scenario '{scenario_name}'.\n{e}"
                )
        else:
            self.logger.warning(
                f"Attempted to load a non-existent scenario: {scenario_name}"
            )

    def _on_save_scenario(self, scenario_name: str):
        self.logger.info(f"Saving scenario: {scenario_name}")
        self.scenario.targets = {t.target_id: t for t in self.target_list.get_targets()}
        self.config_manager.save_scenario(scenario_name, self.scenario.to_dict())
        self.current_scenario_name = scenario_name
        self._load_scenarios_into_ui()
        self._update_window_title()
        messagebox.showinfo(
            "Success", f"Scenario '{scenario_name}' saved successfully.", parent=self
        )

    def _on_save_scenario_as(self, scenario_name: str):
        self.scenario.targets = {t.target_id: t for t in self.target_list.get_targets()}
        self._on_save_scenario(scenario_name)

    def _on_new_scenario(self, scenario_name: str):
        scenario_names = self.config_manager.get_scenario_names()
        if scenario_name in scenario_names:
            messagebox.showinfo(
                "Duplicate Scenario",
                f"Scenario '{scenario_name}' already exists. Loading it instead.",
                parent=self,
            )
            self._on_load_scenario(scenario_name)
            return

        self.logger.info(f"Creating new scenario: {scenario_name}")
        self.scenario = Scenario(name=scenario_name)
        self.current_scenario_name = scenario_name

        # Save the new empty scenario immediately so it's persisted
        self.config_manager.save_scenario(scenario_name, self.scenario.to_dict())

        # Update all UI elements
        self._update_all_views()  # Clears targets, updates title
        self._load_scenarios_into_ui()  # Reloads scenario lists in comboboxes

        # After reloading, explicitly set the current selection to the new scenario
        self.scenario_controls.current_scenario.set(scenario_name)
        self.sim_scenario_combobox.set(scenario_name)

    def _on_delete_scenario(self, scenario_name: str):
        self.logger.info(f"Deleting scenario: {scenario_name}")
        self.config_manager.delete_scenario(scenario_name)
        if self.current_scenario_name == scenario_name:
            self.current_scenario_name = None
            self.scenario = Scenario()
            self._update_all_views()
        self._load_scenarios_into_ui()

    def _on_send_lru_status(self):
        # Implementation from your code
        pass

    def _on_send_initial_state(self):
        """Sends the full scenario using tgtinit for initial setup."""
        if self.target_communicator and self.target_communicator.is_open:
            if self.scenario and self.scenario.get_all_targets():
                # reset_simulation() assicura che i dati inviati siano quelli iniziali
                self.scenario.reset_simulation()
                self.target_communicator.send_scenario(self.scenario)
                messagebox.showinfo(
                    "Scenario Sent",
                    "Initial state of the scenario sent to the radar.",
                    parent=self,
                )
            else:
                messagebox.showinfo(
                    "Empty Scenario", "Cannot send an empty scenario.", parent=self
                )
        else:
            messagebox.showerror(
                "Not Connected",
                "Target communicator is not connected. Please check settings.",
                parent=self,
            )

    def _on_reset_targets(self):
        # Implementation from your code
        pass

    def _open_radar_config(self):
        self.logger.info("Opening radar config window.")
        dialog = RadarConfigWindow(
            self, current_scan_limit=self.scan_limit, current_max_range=self.max_range
        )

        # wait_window è già gestito all'interno di RadarConfigWindow,
        # quindi il codice prosegue solo dopo la sua chiusura.

        if dialog.scan_limit is not None and dialog.max_range is not None:
            # Check if values have actually changed to avoid unnecessary redraws
            if (
                self.scan_limit != dialog.scan_limit
                or self.max_range != dialog.max_range
            ):
                self.logger.info("Radar configuration changed. Applying new settings.")
                self.scan_limit = dialog.scan_limit
                self.max_range = dialog.max_range

                # --- LOGICA MODIFICATA ---
                # Non distruggere il widget, ma riconfiguralo.
                self.ppi_widget.reconfigure_radar(
                    max_range_nm=self.max_range, scan_limit_deg=self.scan_limit
                )

                self.logger.info(f"Scan limit set to: ±{self.scan_limit} degrees")
                self.logger.info(f"Max range set to: {self.max_range} NM")

                # Non è necessario chiamare _update_all_views() perché
                # reconfigure_radar forza già un ridisegno completo.
            else:
                self.logger.info(
                    "Radar configuration confirmed, but no changes were made."
                )

    def _on_closing(self):
        self.logger.info("Application shutting down.")
        if self.is_simulation_running.get():
            self._on_stop_simulation()

        settings_to_save = {
            "scan_limit": self.scan_limit,
            "max_range": self.max_range,
            "geometry": self.winfo_geometry(),
            "last_selected_scenario": self.current_scenario_name,
        }
        self.config_manager.save_general_settings(settings_to_save)
        self.config_manager.save_connection_settings(self.connection_config)

        if self.target_communicator and self.target_communicator.is_open:
            self.target_communicator.disconnect()
        if self.lru_communicator and self.lru_communicator.is_open:
            self.lru_communicator.disconnect()

        shutdown_logging_system()
        self.destroy()

    def _open_sfp_debug_window(self):
        """Opens the SFP debug window, ensuring only one instance exists."""
        if self.sfp_debug_window and self.sfp_debug_window.winfo_exists():
            self.sfp_debug_window.lift()
            self.sfp_debug_window.focus_force()
            self.logger.info("SFP Packet Inspector window is already open.")
            return

        self.logger.info("Opening SFP Packet Inspector window...")
        self.sfp_debug_window = SfpDebugWindow(self)
