# radar_data_reader/gui/profile_editor_window.py

import tkinter as tk
from tkinter import ttk, simpledialog, messagebox
import ctypes
import copy
import re
import inspect
from typing import List, Type, Dict, Any, Optional

from .gui_utils import center_window
from ..core import data_structures as ds
from ..core.data_enums import ENUM_REGISTRY
from ..core.export_profiles import ExportProfile, ExportField
from ..utils import logger

log = logger.get_logger(__name__)


class EditPathDialog(tk.Toplevel):
    def __init__(self, parent, initial_value=""):
        super().__init__(parent)
        self.transient(parent)
        self.grab_set()
        self.title("Edit Data Path")
        self.withdraw()
        self.result = None
        main_frame = ttk.Frame(self, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        main_frame.columnconfigure(0, weight=1)
        instructions = "Enter the full data path. Use '.' for attributes and '[index]' for arrays.\nExample 1: main_header.ge_header.mode.master_mode\nExample 2: timer_data.blob.payload.aesa_delay[0].fifo[3]\nExample 3: d1553_data.latitude_deg (for calculated properties)"
        ttk.Label(main_frame, text=instructions, justify=tk.LEFT).grid(
            row=0, column=0, sticky="w", pady=(0, 10)
        )
        self.path_var = tk.StringVar(value=initial_value)
        self.path_entry = ttk.Entry(
            main_frame, textvariable=self.path_var, font=("Courier", 10), width=90
        )
        self.path_entry.grid(row=1, column=0, sticky="ew")
        self.path_entry.focus_set()
        self.path_entry.selection_range(0, tk.END)
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=2, column=0, sticky="e", pady=(10, 0))
        ttk.Button(
            button_frame, text="OK", command=self._on_ok, default=tk.ACTIVE
        ).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(
            side=tk.LEFT
        )
        self.protocol("WM_DELETE_WINDOW", self._on_cancel)
        self.bind("<Return>", self._on_ok)
        self.bind("<Escape>", self._on_cancel)
        center_window(self, parent)

    def _on_ok(self, event=None):
        self.result = self.path_var.get().strip()
        self.destroy()

    def _on_cancel(self, event=None):
        self.result = None
        self.destroy()

    def wait_for_input(self):
        self.wait_window()
        return self.result


class ProfileEditorWindow(tk.Toplevel):
    def __init__(self, master, controller, profiles: List[ExportProfile]):
        super().__init__(master)
        self.master = master
        self.controller = controller
        self.profiles = copy.deepcopy(profiles)
        self._original_profiles_dict = {p.name: p.to_dict() for p in self.profiles}
        self.withdraw()
        self._init_window()
        self._init_vars()
        self._create_widgets()
        self._populate_available_fields_tree()
        self._load_profiles_to_combobox()
        center_window(self, self.master)
        self.protocol("WM_DELETE_WINDOW", self._on_close)

    def _init_window(self):
        self.title("Export Profile Editor")
        self.geometry("1200x700")
        self.transient(self.master)

    def _init_vars(self):
        self.selected_profile_name = tk.StringVar()

    def _create_widgets(self):
        main_pane = ttk.PanedWindow(self, orient=tk.HORIZONTAL)
        main_pane.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        profile_mgmt_frame = ttk.LabelFrame(main_pane, text="Profiles")
        main_pane.add(profile_mgmt_frame, weight=2)
        profile_mgmt_frame.columnconfigure(0, weight=1)
        cb_frame = ttk.Frame(profile_mgmt_frame)
        cb_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
        cb_frame.columnconfigure(0, weight=1)
        self.profile_combobox = ttk.Combobox(
            cb_frame, textvariable=self.selected_profile_name, state="readonly"
        )
        self.profile_combobox.grid(row=0, column=0, sticky="ew")
        self.profile_combobox.bind("<<ComboboxSelected>>", self._on_profile_selected)
        btn_frame = ttk.Frame(profile_mgmt_frame)
        btn_frame.grid(row=1, column=0, sticky="ew", padx=5)
        btn_frame.columnconfigure((0, 1), weight=1)
        ttk.Button(btn_frame, text="New", command=self._on_new_profile).grid(
            row=0, column=0, sticky="ew", padx=2
        )
        ttk.Button(btn_frame, text="Delete", command=self._on_delete_profile).grid(
            row=0, column=1, sticky="ew", padx=2
        )
        fields_frame = ttk.LabelFrame(main_pane, text="Available Fields")
        main_pane.add(fields_frame, weight=3)
        fields_frame.rowconfigure(0, weight=1)
        fields_frame.columnconfigure(0, weight=1)
        self.fields_tree = ttk.Treeview(fields_frame, selectmode="browse")
        self.fields_tree.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
        self.fields_tree.tag_configure(
            "used_field", foreground="gray", font=("", 0, "italic")
        )
        ysb = ttk.Scrollbar(
            fields_frame, orient="vertical", command=self.fields_tree.yview
        )
        self.fields_tree.configure(yscrollcommand=ysb.set)
        ysb.grid(row=0, column=1, sticky="ns")
        selected_frame_container = ttk.Frame(main_pane)
        main_pane.add(selected_frame_container, weight=5)
        selected_frame_container.rowconfigure(0, weight=1)
        selected_frame_container.columnconfigure(1, weight=1)
        action_btn_frame = ttk.Frame(selected_frame_container)
        action_btn_frame.grid(row=0, column=0, sticky="ns", padx=5, pady=5)
        ttk.Button(action_btn_frame, text=">>", command=self._add_field).grid(pady=5)
        ttk.Button(action_btn_frame, text="<<", command=self._remove_field).grid(pady=5)
        ttk.Button(
            action_btn_frame, text="Up", command=lambda: self._move_field(-1)
        ).grid(pady=10)
        ttk.Button(
            action_btn_frame, text="Down", command=lambda: self._move_field(1)
        ).grid(pady=5)
        ttk.Button(
            action_btn_frame, text="Edit Path", command=self._edit_selected_field_path
        ).grid(pady=10)
        ttk.Button(
            action_btn_frame, text="Reset", command=self._clear_selected_fields
        ).grid(pady=5)
        selected_fields_frame = ttk.LabelFrame(
            selected_frame_container, text="Selected Fields for Profile"
        )
        selected_fields_frame.grid(row=0, column=1, sticky="nsew")
        selected_fields_frame.rowconfigure(0, weight=1)
        selected_fields_frame.columnconfigure(0, weight=1)
        self.selected_tree = ttk.Treeview(
            selected_fields_frame,
            columns=("display_name", "data_path", "translate"),
            show="headings",
            selectmode="browse",
        )
        self.selected_tree.tag_configure("translatable", foreground="#007acc")
        self.selected_tree.tag_configure("not_translatable", foreground="black")

        self.selected_tree.heading("display_name", text="Field Name")
        self.selected_tree.heading("data_path", text="Source Path")
        self.selected_tree.heading("translate", text="Translate")
        self.selected_tree.column("display_name", width=150, stretch=True)
        self.selected_tree.column("data_path", width=300, stretch=True)
        self.selected_tree.column("translate", width=80, anchor="center", stretch=False)
        self.selected_tree.grid(row=0, column=0, sticky="nsew")
        self.selected_tree.bind("<Button-1>", self._on_selected_tree_click)
        self.selected_tree.bind("<Double-1>", self._on_selected_tree_double_click)
        bottom_frame = ttk.Frame(self)
        bottom_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
        ttk.Button(
            bottom_frame, text="Save & Close", command=self._on_save_and_close
        ).pack(side=tk.RIGHT)
        ttk.Button(bottom_frame, text="Cancel", command=self._on_close).pack(
            side=tk.RIGHT, padx=5
        )

    def _on_selected_tree_double_click(self, event):
        if self.selected_tree.identify_row(event.y):
            self._edit_selected_field_path()

    def _edit_selected_field_path(self):
        selection = self.selected_tree.selection()
        if not selection:
            messagebox.showinfo(
                "No Selection", "Please select a field to edit.", parent=self
            )
            return
        index = int(selection[0])
        profile = self._get_current_profile()
        if not profile:
            return
        field = profile.fields[index]
        dialog = EditPathDialog(self, initial_value=field.data_path)
        new_path = dialog.wait_for_input()
        if new_path is not None and new_path != field.data_path:
            if not re.match(r"^[\w\.\[\]_]+$", new_path):
                messagebox.showerror(
                    "Invalid Path", "The path contains invalid characters.", parent=self
                )
                return
            field.data_path = new_path
            self._load_profile_into_ui()

    def _on_selected_tree_click(self, event):
        region = self.selected_tree.identify_region(event.x, event.y)
        if region != "cell":
            return
        column_id = self.selected_tree.identify_column(event.x)
        if column_id != "#3":
            return
        item_id = self.selected_tree.identify_row(event.y)
        if not item_id:
            return
        profile = self._get_current_profile()
        if not profile:
            return
        field_index = int(item_id)
        field = profile.fields[field_index]
        base_path = re.sub(r"\[\d+\]", "", field.data_path)
        if base_path in ENUM_REGISTRY:
            field.translate_with_enum = not field.translate_with_enum
            self._load_profile_into_ui()

    def _load_profile_into_ui(self):
        for i in self.selected_tree.get_children():
            self.selected_tree.delete(i)
        profile = self._get_current_profile()
        if not profile:
            self._update_available_fields_tree_tags()
            return

        for index, field in enumerate(profile.fields):
            base_path = re.sub(r"\[\d+\]", "", field.data_path)
            is_translatable = base_path in ENUM_REGISTRY
            checkbox_char = "☑" if field.translate_with_enum else "☐"
            tag = "translatable" if is_translatable else "not_translatable"
            self.selected_tree.insert(
                "",
                "end",
                iid=str(index),
                values=(field.column_name, field.data_path, checkbox_char),
                tags=(tag,),
            )
        self._update_available_fields_tree_tags()

    def _clear_selected_fields(self):
        profile = self._get_current_profile()
        if not profile or not profile.fields:
            return
        if messagebox.askyesno(
            "Confirm Clear",
            f"Are you sure you want to remove all fields from the profile '{profile.name}'?",
            parent=self,
        ):
            profile.fields.clear()
            self._load_profile_into_ui()

    def _populate_available_fields_tree(self):
        self.fields_tree.delete(*self.fields_tree.get_children())

        def add_virtual_root(iid, text):
            return self.fields_tree.insert("", "end", iid=iid, text=text, open=False)

        batch_root = add_virtual_root("batch_properties", "Batch Properties")
        self._recursive_populate_properties(
            ds.DataBatch,
            batch_root,
            "",
            [
                "blocks", "main_header", "cdp_sts_results", "d1553_data",
                "timer_data", "aesa_data", "exp_data", "pc_data", "det_data",
                "stt_data"
            ],
        )

        header_root = add_virtual_root("header_data", "Header Data (from DSPHDRIN/DSPS)")
        self._recursive_populate_tree_ctypes(
            ds.GeHeader, header_root, "main_header.ge_header"
        )
        
        # --- NUOVA SEZIONE PER I DATI DI TRACKING ---
        stt_root = add_virtual_root("stt_data", "Tracking Data (from STT/DSPHDROUT)")
        self._recursive_populate_tree_ctypes(
            ds.LocalAATrackingData, stt_root, "stt_data.tracking_data"
        )

        cdpsts_root = add_virtual_root("cdpsts_data", "CDP/STS Block Data")
        self._recursive_populate_tree_ctypes(
            ds.CdpStsPayload, cdpsts_root, "cdp_sts_results.payload"
        )

        d1553_root = add_virtual_root("d1553_data", "D1553 Block Data")
        d1553_nav_root = self.fields_tree.insert(
            d1553_root, "end", iid="d1553_nav", text="Navigation Data (Calculated)"
        )
        d1553_status_root = self.fields_tree.insert(
            d1553_root, "end", iid="d1553_status", text="Status Data (Calculated)"
        )
        nav_props = [
            "mission_timestamp_str", "latitude_deg", "longitude_deg", "baro_altitude_m",
            "true_heading_deg", "magnetic_heading_deg", "platform_azimuth_deg",
            "north_velocity_ms", "east_velocity_ms", "down_velocity_ms",
            "ground_track_angle_deg",
        ]
        status_props = ["range_scale_nm", "raw_range_scale", "raw_mode"]
        self._recursive_populate_properties(
            ds.D1553Block, d1553_nav_root, "d1553_data", only_props=nav_props
        )
        self._recursive_populate_properties(
            ds.D1553Block, d1553_status_root, "d1553_data", only_props=status_props
        )

        timer_root = add_virtual_root("timer_data", "Timer Block Data")
        self._recursive_populate_tree_ctypes(
            ds.GrifoTimerBlob, timer_root, "timer_data.blob"
        )

    def _update_available_fields_tree_tags(self):
        profile = self._get_current_profile()
        used_paths = {f.data_path for f in profile.fields} if profile else set()

        for item_id in self._get_all_tree_children(self.fields_tree, ""):
            item_values = self.fields_tree.item(item_id, "values")
            if item_values and len(item_values) >= 2:
                data_path = item_values[1]
                if data_path in used_paths:
                    self.fields_tree.item(item_id, tags=("used_field",))
                else:
                    self.fields_tree.item(item_id, tags=())

    def _get_all_tree_children(self, tree, item):
        children = tree.get_children(item)
        for child in children:
            yield child
            yield from self._get_all_tree_children(tree, child)

    def _recursive_populate_tree_ctypes(
        self, class_obj: Type[ctypes.Structure], parent_id: str, base_path: str
    ):
        if not hasattr(class_obj, "_fields_"):
            return
        for field_name, field_type in class_obj._fields_:
            current_path = f"{base_path}.{field_name}"
            node_id = f"{parent_id}_{field_name}"
            if hasattr(field_type, "_fields_"):
                child_node = self.fields_tree.insert(
                    parent_id, "end", iid=node_id, text=field_name, open=False
                )
                self._recursive_populate_tree_ctypes(
                    field_type, child_node, current_path
                )
            elif hasattr(field_type, "_length_"):
                self.fields_tree.insert(
                    parent_id,
                    "end",
                    iid=node_id,
                    text=f"{field_name} [Array]",
                    values=(field_name, current_path),
                )
            else:
                display_text = f"{field_name}"
                if current_path in ENUM_REGISTRY:
                    display_text += " (Enum)"
                self.fields_tree.insert(
                    parent_id,
                    "end",
                    iid=node_id,
                    text=display_text,
                    values=(field_name, current_path),
                )

    def _recursive_populate_properties(
        self,
        class_obj: Type[Any],
        parent_id: str,
        base_path: str,
        excluded_props: List[str] = [],
        only_props: Optional[List[str]] = None,
    ):
        for name in dir(class_obj):
            if name.startswith("_") or name in excluded_props:
                continue
            if only_props and name not in only_props:
                continue

            if isinstance(getattr(class_obj, name, None), property):
                display_text = name
                current_path = f"{base_path}.{name}"
                if current_path in ENUM_REGISTRY:
                    display_text += " (Enum)"
                self.fields_tree.insert(
                    parent_id,
                    "end",
                    iid=f"{parent_id}_{name}",
                    text=display_text,
                    values=(name, current_path),
                )

    def _load_profiles_to_combobox(self):
        profile_names = [p.name for p in self.profiles]
        self.profile_combobox["values"] = profile_names
        if profile_names:
            self.selected_profile_name.set(profile_names[0])
        else:
            self.selected_profile_name.set("")
        self._load_profile_into_ui()

    def _get_current_profile(self) -> Optional[ExportProfile]:
        name = self.selected_profile_name.get()
        return next((p for p in self.profiles if p.name == name), None)

    def _on_profile_selected(self, event=None):
        self._load_profile_into_ui()

    def _add_field(self):
        selected_item_id = self.fields_tree.focus()
        if not selected_item_id:
            return

        item_values = self.fields_tree.item(selected_item_id, "values")
        if not item_values or len(item_values) < 2:
            messagebox.showinfo(
                "Cannot Add Field",
                "Please select a specific data field, not a category.",
                parent=self,
            )
            return

        tags = self.fields_tree.item(selected_item_id, "tags")
        if "used_field" in tags:
            messagebox.showinfo(
                "Duplicate Field",
                "This field is already in the current profile.",
                parent=self,
            )
            return

        column_name, data_path = item_values
        profile = self._get_current_profile()
        if not profile:
            return

        profile.fields.append(ExportField(column_name=column_name, data_path=data_path))
        self._load_profile_into_ui()

    def _remove_field(self):
        selection = self.selected_tree.selection()
        if not selection:
            return
        index_to_remove = int(selection[0])
        profile = self._get_current_profile()
        if not profile:
            return

        del profile.fields[index_to_remove]
        self._load_profile_into_ui()

    def _move_field(self, direction: int):
        selection = self.selected_tree.selection()
        if not selection:
            return
        index = int(selection[0])
        new_index = index + direction
        profile = self._get_current_profile()
        if not profile or not (0 <= new_index < len(profile.fields)):
            return

        fields = profile.fields
        fields.insert(new_index, fields.pop(index))
        self._load_profile_into_ui()
        self.selected_tree.selection_set(str(new_index))

    def _on_new_profile(self):
        name = simpledialog.askstring(
            "New Profile", "Enter a name for the new profile:", parent=self
        )
        if not name or not name.strip():
            return
        if any(p.name == name for p in self.profiles):
            messagebox.showerror(
                "Error",
                f"A profile with the name '{name}' already exists.",
                parent=self,
            )
            return

        new_profile = ExportProfile(name=name.strip())
        self.profiles.append(new_profile)
        self._load_profiles_to_combobox()
        self.selected_profile_name.set(name)
        self._load_profile_into_ui()

    def _on_delete_profile(self):
        profile = self._get_current_profile()
        if not profile:
            return
        if messagebox.askyesno(
            "Confirm Delete",
            f"Are you sure you want to delete the profile '{profile.name}'?",
            parent=self,
        ):
            self.profiles.remove(profile)
            self._load_profiles_to_combobox()

    def _check_unsaved_changes(self) -> bool:
        current_profiles_dict = {p.name: p.to_dict() for p in self.profiles}
        return current_profiles_dict != self._original_profiles_dict

    def _on_save_and_close(self):
        log.info("Saving export profiles and closing editor.")
        self.controller.save_export_profiles(self.profiles)
        self.destroy()

    def _on_close(self):
        if self._check_unsaved_changes():
            response = messagebox.askyesnocancel(
                "Unsaved Changes",
                "You have unsaved changes. Would you like to save them?",
                parent=self,
            )
            if response is True:
                self._on_save_and_close()
            elif response is False:
                self.destroy()
        else:
            self.destroy()