# target_simulator/gui/sync_tool_window.py
import tkinter as tk
from tkinter import ttk, messagebox
import time
import random
import os
import csv
import threading
from typing import Dict, Optional, List
from collections import deque
from datetime import datetime

from target_simulator.core.sfp_communicator import SFPCommunicator
from target_simulator.gui.payload_router import DebugPayloadRouter

# Tentativo di importare matplotlib per il grafico
try:
    import matplotlib

    matplotlib.use("TkAgg")
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    from matplotlib.figure import Figure

    MATPLOTLIB_AVAILABLE = True
except ImportError:
    MATPLOTLIB_AVAILABLE = False


class SyncToolWindow(tk.Toplevel):
    """Finestra per il test di latenza tramite messaggi SYNC con grafico in tempo reale."""

    def __init__(
        self, master, communicator: SFPCommunicator, router: DebugPayloadRouter
    ):
        super().__init__(master)
        self.communicator = communicator
        self.router = router

        self.title("Latency Sync Tool")
        self.geometry("900x700")
        self.transient(master)
        self.grab_set()

        self.pending_requests: Dict[int, float] = {}  # {cookie: send_timestamp}

        # Stato per invio periodico
        self.periodic_active = False
        self.periodic_interval_ms = 500  # Default 500ms
        self.periodic_after_id = None

        # Storico delle latenze per il grafico (max 200 campioni)
        self.latency_history: deque = deque(maxlen=200)
        self.time_history: deque = deque(maxlen=200)

        # Statistiche
        self.latency_values: List[float] = []

        # === Sistema di salvataggio CSV (asincrono e non invasivo) ===
        self.csv_enabled = False
        self.csv_filepath: Optional[str] = None
        self.csv_buffer: deque = deque(maxlen=10000)  # Buffer grande per evitare perdite
        self.csv_lock = threading.Lock()
        self.csv_writer_thread: Optional[threading.Thread] = None
        self.csv_stop_event = threading.Event()
        self.csv_session_start: Optional[float] = None

        self._create_widgets()

        self.protocol("WM_DELETE_WINDOW", self._on_close)
        self.after(100, self._process_sync_queue)

    def _create_widgets(self):
        main_frame = ttk.Frame(self, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)

        # === CONTROLLI SUPERIORI ===
        controls_frame = ttk.LabelFrame(main_frame, text="Controls", padding=5)
        controls_frame.pack(fill=tk.X, pady=(0, 10))

        # Riga 1: Invio singolo
        row1 = ttk.Frame(controls_frame)
        row1.pack(fill=tk.X, pady=2)

        self.send_button = ttk.Button(
            row1, text="Send Single SYNC", command=self._send_sync, width=18
        )
        self.send_button.pack(side=tk.LEFT, padx=(0, 10))

        self.status_var = tk.StringVar(value="Idle")
        ttk.Label(row1, textvariable=self.status_var, foreground="blue").pack(
            side=tk.LEFT
        )

        # Riga 2: Invio periodico
        row2 = ttk.Frame(controls_frame)
        row2.pack(fill=tk.X, pady=2)

        self.start_stop_button = ttk.Button(
            row2, text="Start Periodic", command=self._toggle_periodic, width=18
        )
        self.start_stop_button.pack(side=tk.LEFT, padx=(0, 10))

        ttk.Label(row2, text="Interval (ms):").pack(side=tk.LEFT, padx=(0, 5))
        self.interval_var = tk.StringVar(value="500")
        interval_entry = ttk.Entry(row2, textvariable=self.interval_var, width=8)
        interval_entry.pack(side=tk.LEFT, padx=(0, 10))

        ttk.Button(row2, text="Clear History", command=self._clear_history).pack(
            side=tk.LEFT
        )

        # Riga 3: Salvataggio CSV
        row3 = ttk.Frame(controls_frame)
        row3.pack(fill=tk.X, pady=2)

        self.csv_check_var = tk.BooleanVar(value=False)
        self.csv_checkbox = ttk.Checkbutton(
            row3,
            text="Save to CSV",
            variable=self.csv_check_var,
            command=self._toggle_csv_save,
        )
        self.csv_checkbox.pack(side=tk.LEFT, padx=(0, 10))

        self.csv_status_var = tk.StringVar(value="Not saving")
        self.csv_status_label = tk.Label(
            row3, textvariable=self.csv_status_var, foreground="gray"
        )
        self.csv_status_label.pack(side=tk.LEFT)

        # === STATISTICHE ===
        stats_frame = ttk.LabelFrame(main_frame, text="Statistics", padding=5)
        stats_frame.pack(fill=tk.X, pady=(0, 10))

        stats_grid = ttk.Frame(stats_frame)
        stats_grid.pack(fill=tk.X)

        ttk.Label(stats_grid, text="Samples:").grid(
            row=0, column=0, sticky=tk.W, padx=5
        )
        self.samples_var = tk.StringVar(value="0")
        ttk.Label(
            stats_grid,
            textvariable=self.samples_var,
            font=("TkDefaultFont", 10, "bold"),
        ).grid(row=0, column=1, sticky=tk.W, padx=5)

        ttk.Label(stats_grid, text="Last:").grid(row=0, column=2, sticky=tk.W, padx=5)
        self.last_var = tk.StringVar(value="-- ms")
        ttk.Label(
            stats_grid, textvariable=self.last_var, font=("TkDefaultFont", 10, "bold")
        ).grid(row=0, column=3, sticky=tk.W, padx=5)

        ttk.Label(stats_grid, text="Min:").grid(row=0, column=4, sticky=tk.W, padx=5)
        self.min_var = tk.StringVar(value="-- ms")
        ttk.Label(
            stats_grid, textvariable=self.min_var, font=("TkDefaultFont", 10, "bold")
        ).grid(row=0, column=5, sticky=tk.W, padx=5)

        ttk.Label(stats_grid, text="Max:").grid(row=0, column=6, sticky=tk.W, padx=5)
        self.max_var = tk.StringVar(value="-- ms")
        ttk.Label(
            stats_grid, textvariable=self.max_var, font=("TkDefaultFont", 10, "bold")
        ).grid(row=0, column=7, sticky=tk.W, padx=5)

        ttk.Label(stats_grid, text="Avg:").grid(row=0, column=8, sticky=tk.W, padx=5)
        self.avg_var = tk.StringVar(value="-- ms")
        ttk.Label(
            stats_grid, textvariable=self.avg_var, font=("TkDefaultFont", 10, "bold")
        ).grid(row=0, column=9, sticky=tk.W, padx=5)

        # === GRAFICO ===
        if MATPLOTLIB_AVAILABLE:
            graph_frame = ttk.LabelFrame(main_frame, text="Latency Graph", padding=5)
            graph_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

            self.figure = Figure(figsize=(8, 3), dpi=100)
            self.ax = self.figure.add_subplot(111)
            self.ax.set_xlabel("Time (s)")
            self.ax.set_ylabel("Latency (ms)")
            self.ax.set_title("One-Way Latency Over Time")
            self.ax.grid(True, alpha=0.3)

            self.canvas = FigureCanvasTkAgg(self.figure, graph_frame)
            self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

            (self.line,) = self.ax.plot([], [], "b-", linewidth=1.5)
        else:
            graph_frame = ttk.LabelFrame(main_frame, text="Latency Graph", padding=5)
            graph_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
            ttk.Label(
                graph_frame,
                text="Matplotlib not available. Install with: pip install matplotlib",
                foreground="red",
            ).pack(pady=20)

        # === TABELLA RISULTATI ===
        tree_frame = ttk.LabelFrame(main_frame, text="Recent Results", padding=5)
        tree_frame.pack(fill=tk.BOTH, expand=True)

        columns = ("time", "rtt", "latency")
        self.tree = ttk.Treeview(tree_frame, columns=columns, show="headings", height=8)
        self.tree.heading("time", text="Timestamp")
        self.tree.heading("rtt", text="Round Trip (ms)")
        self.tree.heading("latency", text="One-Way Latency (ms)")

        self.tree.column("time", width=150)
        self.tree.column("rtt", width=150, anchor=tk.E)
        self.tree.column("latency", width=150, anchor=tk.E)

        scrollbar = ttk.Scrollbar(
            tree_frame, orient=tk.VERTICAL, command=self.tree.yview
        )
        self.tree.configure(yscrollcommand=scrollbar.set)

        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

    def _send_sync(self):
        """Invia una singola richiesta SYNC."""
        if not self.communicator or not self.communicator.is_open:
            messagebox.showerror(
                "Error", "SFP Communicator is not connected.", parent=self
            )
            return

        cookie = random.randint(0, 2**32 - 1)
        send_time = time.monotonic()

        if self.communicator.send_sync_request(cookie):
            self.pending_requests[cookie] = send_time
            if not self.periodic_active:
                self.status_var.set(
                    f"Sent request with cookie {cookie}. Waiting for reply..."
                )
        else:
            self.status_var.set("Failed to send SYNC request.")

    def _toggle_periodic(self):
        """Attiva/disattiva l'invio periodico."""
        if self.periodic_active:
            # Stop
            self.periodic_active = False
            if self.periodic_after_id:
                self.after_cancel(self.periodic_after_id)
                self.periodic_after_id = None
            self.start_stop_button.config(text="Start Periodic")
            self.status_var.set("Periodic sending stopped.")
        else:
            # Start
            try:
                interval = int(self.interval_var.get())
                if interval < 50:
                    messagebox.showwarning(
                        "Warning", "Interval too short. Minimum is 50ms.", parent=self
                    )
                    return
                self.periodic_interval_ms = interval
            except ValueError:
                messagebox.showerror("Error", "Invalid interval value.", parent=self)
                return

            self.periodic_active = True
            self.start_stop_button.config(text="Stop Periodic")
            self.status_var.set(
                f"Periodic sending active (every {self.periodic_interval_ms} ms)"
            )
            self._periodic_send()

    def _periodic_send(self):
        """Invia periodicamente richieste SYNC."""
        if not self.periodic_active:
            return

        self._send_sync()
        self.periodic_after_id = self.after(
            self.periodic_interval_ms, self._periodic_send
        )

    def _clear_history(self):
        """Pulisce lo storico e le statistiche."""
        self.latency_history.clear()
        self.time_history.clear()
        self.latency_values.clear()
        self._update_statistics()
        self._update_graph()

        # Pulisce anche la tabella
        for item in self.tree.get_children():
            self.tree.delete(item)

    def _update_statistics(self):
        """Aggiorna le statistiche visualizzate."""
        if not self.latency_values:
            self.samples_var.set("0")
            self.last_var.set("-- ms")
            self.min_var.set("-- ms")
            self.max_var.set("-- ms")
            self.avg_var.set("-- ms")
        else:
            self.samples_var.set(str(len(self.latency_values)))
            self.last_var.set(f"{self.latency_values[-1]:.2f} ms")
            self.min_var.set(f"{min(self.latency_values):.2f} ms")
            self.max_var.set(f"{max(self.latency_values):.2f} ms")
            self.avg_var.set(
                f"{sum(self.latency_values)/len(self.latency_values):.2f} ms"
            )

    def _update_graph(self):
        """Aggiorna il grafico della latenza."""
        if not MATPLOTLIB_AVAILABLE:
            return

        if len(self.time_history) > 0:
            # Converti i tempi assoluti in relativi (secondi dall'inizio)
            times = list(self.time_history)
            latencies = list(self.latency_history)

            start_time = times[0]
            relative_times = [(t - start_time) for t in times]

            self.line.set_data(relative_times, latencies)

            self.ax.relim()
            self.ax.autoscale_view()

            try:
                self.canvas.draw()
            except:
                pass  # Ignora errori durante il disegno

    def _process_sync_queue(self):
        """Estrae i risultati dalla coda del router e aggiorna la UI."""
        result = self.router.get_sync_result()
        if result:
            cookie = result.get("cookie")
            reception_time = result.get("reception_timestamp")

            if cookie in self.pending_requests:
                send_time = self.pending_requests.pop(cookie)

                rtt_s = reception_time - send_time
                rtt_ms = rtt_s * 1000
                latency_ms = rtt_ms / 2.0

                # Aggiorna storico
                self.latency_history.append(latency_ms)
                self.time_history.append(reception_time)
                self.latency_values.append(latency_ms)

                # Limita latency_values per non far crescere troppo la lista
                if len(self.latency_values) > 200:
                    self.latency_values.pop(0)

                # Salva su CSV se abilitato (operazione velocissima, non blocca)
                if self.csv_enabled and self.csv_session_start is not None:
                    elapsed_s = reception_time - self.csv_session_start
                    server_timetag = result.get("server_timetag", 0)
                    self._add_sample_to_csv_buffer(
                        elapsed_s, cookie, rtt_ms, latency_ms, server_timetag
                    )

                # Aggiorna UI
                timestamp_str = time.strftime("%H:%M:%S")

                self.tree.insert(
                    "", 0, values=(timestamp_str, f"{rtt_ms:.3f}", f"{latency_ms:.3f}")
                )

                # Limita il numero di righe nella tabella
                items = self.tree.get_children()
                if len(items) > 50:
                    self.tree.delete(items[-1])

                if not self.periodic_active:
                    self.status_var.set(
                        f"Received reply for cookie {cookie}. Latency: {latency_ms:.2f} ms"
                    )

                self._update_statistics()
                self._update_graph()

        # Continua il polling
        self.after(100, self._process_sync_queue)

    def _toggle_csv_save(self):
        """Attiva/disattiva il salvataggio CSV asincrono."""
        if self.csv_check_var.get():
            # Avvia salvataggio
            self._start_csv_save()
        else:
            # Ferma salvataggio
            self._stop_csv_save()

    def _start_csv_save(self):
        """Avvia il salvataggio CSV in background."""
        if self.csv_enabled:
            return  # Già attivo

        # Crea nome file con timestamp
        project_root = os.path.abspath(
            os.path.join(os.path.dirname(__file__), "..", "..")
        )
        temp_dir = os.path.join(project_root, "Temp")
        os.makedirs(temp_dir, exist_ok=True)

        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        self.csv_filepath = os.path.join(temp_dir, f"sync_latency_{timestamp}.csv")
        self.csv_session_start = time.monotonic()

        # Inizializza il file CSV con header
        try:
            with open(self.csv_filepath, "w", newline="") as f:
                writer = csv.writer(f)
                writer.writerow(
                    [
                        "elapsed_s",
                        "timestamp",
                        "cookie",
                        "rtt_ms",
                        "latency_ms",
                        "server_timetag",
                    ]
                )
            self.csv_enabled = True
            self.csv_stop_event.clear()

            # Avvia thread di scrittura
            self.csv_writer_thread = threading.Thread(
                target=self._csv_writer_loop, daemon=True, name="CSVWriterThread"
            )
            self.csv_writer_thread.start()

            filename = os.path.basename(self.csv_filepath)
            self.csv_status_var.set(f"Saving to {filename}")
            self.csv_status_label.config(foreground="green")

        except Exception as e:
            messagebox.showerror(
                "CSV Error", f"Failed to create CSV file: {e}", parent=self
            )
            self.csv_check_var.set(False)
            self.csv_enabled = False

    def _stop_csv_save(self):
        """Ferma il salvataggio CSV."""
        if not self.csv_enabled:
            return

        self.csv_enabled = False
        self.csv_stop_event.set()

        # Attendi che il thread finisca di scrivere i dati rimanenti
        if self.csv_writer_thread and self.csv_writer_thread.is_alive():
            self.csv_writer_thread.join(timeout=2.0)

        self.csv_status_var.set("Not saving")
        self.csv_status_label.config(foreground="gray")

        if self.csv_filepath:
            filename = os.path.basename(self.csv_filepath)
            messagebox.showinfo(
                "CSV Saved",
                f"Latency data saved to:\n{filename}",
                parent=self,
            )

    def _csv_writer_loop(self):
        """Thread worker che scrive i dati bufferizzati su disco periodicamente."""
        while not self.csv_stop_event.is_set():
            try:
                # Attendi un po' prima di scrivere (batch writing)
                time.sleep(1.0)

                # Estrai tutti i dati dal buffer
                rows_to_write = []
                with self.csv_lock:
                    while len(self.csv_buffer) > 0:
                        rows_to_write.append(self.csv_buffer.popleft())

                # Scrivi su file (fuori dal lock per non bloccare)
                if rows_to_write and self.csv_filepath:
                    try:
                        with open(self.csv_filepath, "a", newline="") as f:
                            writer = csv.writer(f)
                            writer.writerows(rows_to_write)
                    except Exception:
                        pass  # Ignora errori di scrittura per non bloccare

            except Exception:
                pass

        # Flush finale dei dati rimanenti
        rows_to_write = []
        with self.csv_lock:
            while len(self.csv_buffer) > 0:
                rows_to_write.append(self.csv_buffer.popleft())

        if rows_to_write and self.csv_filepath:
            try:
                with open(self.csv_filepath, "a", newline="") as f:
                    writer = csv.writer(f)
                    writer.writerows(rows_to_write)
            except Exception:
                pass

    def _add_sample_to_csv_buffer(
        self, elapsed_s: float, cookie: int, rtt_ms: float, latency_ms: float, server_timetag: int
    ):
        """Aggiunge un campione al buffer CSV (operazione velocissima, non blocca)."""
        if not self.csv_enabled:
            return

        timestamp_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
        row = [
            f"{elapsed_s:.3f}",
            timestamp_str,
            cookie,
            f"{rtt_ms:.3f}",
            f"{latency_ms:.3f}",
            server_timetag,
        ]

        with self.csv_lock:
            self.csv_buffer.append(row)

    def _on_close(self):
        """Gestisce la chiusura della finestra."""
        if self.periodic_active:
            self.periodic_active = False
            if self.periodic_after_id:
                self.after_cancel(self.periodic_after_id)

        # Ferma il salvataggio CSV se attivo
        if self.csv_enabled:
            self.csv_check_var.set(False)
            self._stop_csv_save()

        self.destroy()
