# target_simulator/simulation/simulation_controller.py

import time
import threading
from typing import Optional
from tkinter import messagebox

from target_simulator.core.simulation_engine import SimulationEngine
from target_simulator.analysis.simulation_archive import SimulationArchive
from target_simulator.core import command_builder


class SimulationController:
    """Orchestrates simulation start/stop/reset logic extracted from MainView."""

    def __init__(self, communicator_manager, simulation_hub, config_manager, logger):
        self.communicator_manager = communicator_manager
        self.simulation_hub = simulation_hub
        self.config_manager = config_manager
        self.logger = logger
        self.simulation_engine: Optional[SimulationEngine] = None
        self.current_archive: Optional[SimulationArchive] = None

    def _reset_radar_state_no_ui(self, main_view) -> bool:
        """Runs the reset sequence without showing UI popups. Safe for threads."""
        target_comm = getattr(self.communicator_manager, "target_communicator", None)
        if not target_comm or not getattr(target_comm, "is_open", False):
            self.logger.error("Cannot reset radar: communicator is not connected.")
            return False

        use_json = getattr(target_comm, "_use_json_protocol", False)

        def _wait_for_clear(timeout_s: float) -> bool:
            start_time = time.monotonic()
            while time.monotonic() - start_time < timeout_s:
                if not self.simulation_hub.has_active_real_targets():
                    return True
                time.sleep(0.2)
            return False

        if not use_json:
            self.logger.info("Sending legacy reset command...")
            if not target_comm.send_commands(["tgtset /-s\n"]):
                self.logger.error("Failed to send legacy reset command.")
                return False
            if not _wait_for_clear(3.0):
                self.logger.error("Legacy reset failed: server did not clear targets.")
                return False
            return True

        # JSON path with fallback
        self.logger.info("Sending primary JSON reset command...")
        if target_comm.send_commands(['{"CMD":"reset"}\n']):
            if _wait_for_clear(1.0):
                self.logger.info("Primary JSON reset successful.")
                return True
            self.logger.warning("Primary JSON reset timed out. Trying fallback...")
        else:
            self.logger.warning("Primary JSON reset command failed. Trying fallback...")
        
        self.logger.info("Sending fallback per-ID JSON reset payloads...")
        try:
            json_payloads = command_builder.build_json_reset_ids()
        except Exception as e:
            self.logger.exception(f"Failed to build fallback payloads: {e}")
            return False

        for i, payload in enumerate(json_payloads):
            if not target_comm.send_commands([payload]):
                self.logger.error(f"Failed to send fallback payload part {i+1}.")
                return False

        if not _wait_for_clear(3.0):
            self.logger.error("Fallback per-ID JSON reset failed to clear targets.")
            return False

        self.logger.info("Fallback per-ID JSON reset successful.")
        return True

    def start_simulation(self, main_view):
        """Start live simulation asynchronously."""
        target_comm = getattr(self.communicator_manager, "target_communicator", None)
        if not (target_comm and getattr(target_comm, "is_open", False)):
            messagebox.showwarning("Not Connected", "Please connect before starting.")
            main_view._start_in_progress_main = False # Release the guard
            return

        if not main_view.scenario or not main_view.scenario.get_all_targets():
            messagebox.showinfo("Empty Scenario", "Cannot start an empty scenario.")
            main_view._start_in_progress_main = False # Release the guard
            return

        def _background_start():
            self.logger.info("Background start process initiated.")
            reset_ok = self._reset_radar_state_no_ui(main_view)
            if not reset_ok:
                self.logger.error("Radar reset failed. Aborting start.")
                main_view.after(0, lambda: messagebox.showerror(
                    "Start Failed", "Could not reset radar state. Simulation aborted."
                ))
                main_view.after(0, self._finalize_start_failure, main_view)
                return

            self.logger.info("Sending initial scenario state...")
            if not target_comm.send_scenario(main_view.scenario):
                self.logger.error("Failed to send scenario. Aborting start.")
                main_view.after(0, lambda: messagebox.showerror(
                    "Start Failed", "Could not send scenario. Aborted."
                ))
                main_view.after(0, self._finalize_start_failure, main_view)
                return

            try:
                self.logger.info("Initializing SimulationEngine.")
                engine = SimulationEngine(communicator=target_comm, simulation_hub=self.simulation_hub)
                engine.set_update_interval(main_view.update_time.get())
                engine.load_scenario(main_view.scenario)
                main_view.simulation_engine = self.simulation_engine = engine
                
                durations = [getattr(t, "_total_duration_s", 0.0) for t in main_view.scenario.get_all_targets()]
                total_time = max(durations) if durations else 0.0
                archive = SimulationArchive(main_view.scenario)
                engine.archive = archive
                
                if hasattr(target_comm, "router"):
                    router = target_comm.router()
                    if router:
                        router.set_archive(archive)
                
                self.logger.info("Starting SimulationEngine thread.")
                engine.start()
                main_view.after(0, self._finalize_start_success, main_view, archive, total_time)

            except Exception as e:
                self.logger.exception(f"Failed to start SimulationEngine: {e}")
                main_view.after(0, lambda: messagebox.showerror("Start Failed", f"Error: {e}"))
                main_view.after(0, self._finalize_start_failure, main_view)

        # UI updates on main thread
        main_view.show_status_message("Starting simulation...", timeout_ms=None)
        main_view._update_button_states()
        self.simulation_hub.reset()
        if hasattr(main_view, "ppi_widget"):
            main_view.ppi_widget.clear_trails()
        
        threading.Thread(target=_background_start, daemon=True).start()

    def _finalize_start_success(self, main_view, archive, total_time):
        """Callback on main thread after a successful background start."""
        main_view.current_archive = self.current_archive = archive
        main_view.total_sim_time = total_time
        main_view.sim_elapsed_time = 0.0
        main_view.simulation_controls.sim_slider_var.set(0.0)
        main_view._update_simulation_progress_display()
        if hasattr(main_view, "ppi_widget"):
            main_view.ppi_widget.clear_previews()
        
        main_view.is_simulation_running.set(True)
        main_view._start_in_progress_main = False
        main_view._update_button_states()
        main_view.show_status_message("Simulation running", timeout_ms=None)
        self.logger.info("Simulation started successfully.")

    def _finalize_start_failure(self, main_view):
        """Callback on main thread after a failed background start."""
        main_view.is_simulation_running.set(False)
        main_view._start_in_progress_main = False
        main_view._update_button_states()
        main_view.clear_status_message()
        self.logger.warning("Simulation start process failed and was finalized.")

    def _stop_or_finish_simulation(self, main_view, was_stopped_by_user: bool):
        """Unified logic for handling simulation end, either by user or naturally."""
        if self.current_archive:
            # --- NUOVA AGGIUNTA INIZIO ---
            # Retrieve estimated latency before saving the archive
            estimated_latency_s = 0.0
            extra_metadata = {}
            try:
                target_comm = getattr(self.communicator_manager, "target_communicator", None)
                if target_comm and hasattr(target_comm, 'router'):
                    router = target_comm.router()
                    if router and hasattr(router, 'get_estimated_latency_s'):
                        estimated_latency_s = router.get_estimated_latency_s()
                
                if estimated_latency_s > 0:
                    extra_metadata['estimated_latency_ms'] = round(estimated_latency_s * 1000, 2)
                    
                    
                # Retrieve prediction offset from config
                conn_settings = self.config_manager.get_connection_settings()
                target_sfp_cfg = conn_settings.get("target", {}).get("sfp", {})
                offset_ms = target_sfp_cfg.get("prediction_offset_ms", 0.0)
                if offset_ms > 0:
                    extra_metadata['prediction_offset_ms'] = offset_ms
                    
            except Exception as e:
                self.logger.warning(f"Could not retrieve estimated latency for archive: {e}")
            # Also attempt to include latency statistics and recent samples
            try:
                if target_comm and hasattr(target_comm, 'router'):
                    router = target_comm.router()
                    if router and hasattr(router, 'get_latency_stats'):
                        stats = router.get_latency_stats(sample_limit=500)
                        if stats and stats.get('count', 0) > 0:
                            extra_metadata['latency_summary'] = stats
                    if router and hasattr(router, 'get_latency_samples'):
                        samples = router.get_latency_samples(limit=200)
                        if samples:
                            # convert to milliseconds and round
                            samples_ms = [round(s * 1000.0, 3) for s in samples[-200:]]
                            extra_metadata['latency_samples_ms'] = samples_ms
            except Exception as e:
                self.logger.warning(f"Could not collect latency samples for archive: {e}")
            # Add simulation parameters (client update interval / send rate)
            try:
                update_interval_s = None
                if hasattr(main_view, "update_time"):
                    try:
                        # update_time is a Tk variable (DoubleVar) in the UI
                        update_interval_s = float(main_view.update_time.get())
                    except Exception:
                        update_interval_s = None

                if update_interval_s is not None:
                    extra_metadata['client_update_interval_s'] = round(update_interval_s, 6)
                    if update_interval_s > 0:
                        extra_metadata['client_update_rate_hz'] = round(1.0 / update_interval_s, 3)
            except Exception as e:
                self.logger.warning(f"Could not read client update interval for archive: {e}")
            # --- NUOVA AGGIUNTA FINE ---

            self.current_archive.save(extra_metadata=extra_metadata)
            self.current_archive = main_view.current_archive = None
            
            target_comm = getattr(self.communicator_manager, "target_communicator", None)
            if target_comm and hasattr(target_comm, "router"):
                router = target_comm.router()
                if router:
                    router.set_archive(None)
            main_view._refresh_analysis_list()
        
        if self.simulation_engine:
            self.simulation_engine.stop()
            self.simulation_engine = main_view.simulation_engine = None

        main_view.is_simulation_running.set(False)
        main_view._update_button_states()

        if was_stopped_by_user:
            main_view.show_status_message("Simulation stopped.", timeout_ms=5000)
        else:
            main_view.show_status_message("Simulation finished.", timeout_ms=5000)
            main_view.simulation_controls.show_notice("Simulation has finished.")

    def stop_simulation(self, main_view):
        """Handles user request to stop the simulation."""
        if not main_view.is_simulation_running.get():
            return
        self.logger.info("Stopping live simulation (user request)...")
        self._stop_or_finish_simulation(main_view, was_stopped_by_user=True)

    def on_simulation_finished(self, main_view):
        """Handles the natural end of a simulation."""
        if not main_view.is_simulation_running.get():
            return
        self.logger.info("Simulation engine finished execution.")
        self._stop_or_finish_simulation(main_view, was_stopped_by_user=False)