import tkinter as tk
from tkinter import ttk, messagebox
import pandas as pd
import json
import difflib
from typing import List, Dict, Any

class Tooltip:
    """
    Create a tooltip for a given widget.
    """
    def __init__(self, widget, text):
        self.widget = widget
        self.text = text
        self.tooltip_window = None
        self.widget.bind("<Enter>", self.show_tooltip)
        self.widget.bind("<Leave>", self.hide_tooltip)

    def show_tooltip(self, event=None):
        x, y, _, _ = self.widget.bbox("insert")
        x += self.widget.winfo_rootx() + 25
        y += self.widget.winfo_rooty() + 25

        self.tooltip_window = tk.Toplevel(self.widget)
        self.tooltip_window.wm_overrideredirect(True)
        self.tooltip_window.wm_geometry(f"+{x}+{y}")

        label = tk.Label(self.tooltip_window, text=self.text, justify='left',
                         background="#ffffe0", relief='solid', borderwidth=1,
                         font=("tahoma", "8", "normal"))
        label.pack(ipadx=1)

    def hide_tooltip(self, event=None):
        if self.tooltip_window:
            self.tooltip_window.destroy()
        self.tooltip_window = None

class FullValueWindow(tk.Toplevel):
    """A simple window to show the full, pretty-printed value with search functionality."""
    def __init__(self, parent, title: str, value_str: str):
        super().__init__(parent)
        self.title(title)
        self.transient(parent)
        self.grab_set()

        # --- State ---
        self.matches = []
        self.current_match_index = -1
        self.search_term_var = tk.StringVar()
        self.search_status_var = tk.StringVar()

        # --- UI ---
        self.geometry("800x600")
        self._create_widgets(value_str)
        self._bind_events()

    def _create_widgets(self, value_str: str):
        main_frame = ttk.Frame(self)
        main_frame.pack(expand=True, fill=tk.BOTH)
        main_frame.rowconfigure(1, weight=1)
        main_frame.columnconfigure(0, weight=1)

        # --- Search Bar ---
        search_frame = ttk.Frame(main_frame, padding=(5, 5))
        search_frame.grid(row=0, column=0, sticky="ew")
        search_frame.columnconfigure(1, weight=1)

        ttk.Label(search_frame, text="Find:").grid(row=0, column=0, padx=(0, 5))
        
        self.search_entry = ttk.Entry(search_frame, textvariable=self.search_term_var)
        self.search_entry.grid(row=0, column=1, sticky="ew")

        search_button = ttk.Button(search_frame, text="Find", command=self._perform_search)
        search_button.grid(row=0, column=2, padx=5)

        search_status_label = ttk.Label(search_frame, textvariable=self.search_status_var, anchor="w")
        search_status_label.grid(row=0, column=3, padx=5, sticky="w")

        hint_label = ttk.Label(search_frame, text="(F3 for next)", foreground="gray")
        hint_label.grid(row=0, column=4, padx=(5, 0), sticky="w")

        # --- Text Area ---
        text_frame = ttk.Frame(main_frame)
        text_frame.grid(row=1, column=0, sticky="nsew", padx=5, pady=(0,5))
        text_frame.rowconfigure(0, weight=1)
        text_frame.columnconfigure(0, weight=1)

        self.text_widget = tk.Text(text_frame, wrap=tk.NONE, font=("Consolas", 10))
        self.text_widget.grid(row=0, column=0, sticky="nsew")

        v_scroll = ttk.Scrollbar(text_frame, orient="vertical", command=self.text_widget.yview)
        v_scroll.grid(row=0, column=1, sticky="ns")
        h_scroll = ttk.Scrollbar(text_frame, orient="horizontal", command=self.text_widget.xview)
        h_scroll.grid(row=1, column=0, sticky="ew")
        self.text_widget.configure(yscrollcommand=v_scroll.set, xscrollcommand=h_scroll.set)

        # --- Populate and Configure Text ---
        try:
            pretty_value = json.dumps(json.loads(value_str), indent=4)
            self.text_widget.insert("1.0", pretty_value)
        except (json.JSONDecodeError, TypeError):
            self.text_widget.insert("1.0", value_str)
        
        self.text_widget.config(state=tk.DISABLED)
        self.text_widget.tag_configure("match", background="yellow", foreground="black")
        self.text_widget.tag_configure("current_match", background="orange", foreground="black")

    def _bind_events(self):
        self.bind("<Escape>", lambda e: self.destroy())
        self.bind("<F3>", self._find_next)
        self.search_entry.bind("<Return>", self._perform_search)

    def _clear_search(self):
        self.text_widget.config(state=tk.NORMAL)
        self.text_widget.tag_remove("match", "1.0", tk.END)
        self.text_widget.tag_remove("current_match", "1.0", tk.END)
        self.text_widget.config(state=tk.DISABLED)
        self.matches = []
        self.current_match_index = -1
        self.search_status_var.set("")

    def _perform_search(self, event=None):
        self._clear_search()
        term = self.search_term_var.get()
        if not term:
            return

        self.text_widget.config(state=tk.NORMAL)
        
        start_pos = "1.0"
        while True:
            start_pos = self.text_widget.search(term, start_pos, stopindex=tk.END, nocase=True, count=tk.IntVar())
            if not start_pos:
                break
            end_pos = f"{start_pos}+{len(term)}c"
            self.matches.append((start_pos, end_pos))
            self.text_widget.tag_add("match", start_pos, end_pos)
            start_pos = end_pos
        
        self.text_widget.config(state=tk.DISABLED)

        if self.matches:
            self.search_status_var.set(f"{len(self.matches)} found")
            self._find_next()
        else:
            self.search_status_var.set("Not found")

    def _find_next(self, event=None):
        if not self.matches:
            return

        self.text_widget.config(state=tk.NORMAL)
        # Remove tag from previous current match
        if self.current_match_index != -1:
            start, end = self.matches[self.current_match_index]
            self.text_widget.tag_add("match", start, end)
            self.text_widget.tag_remove("current_match", start, end)

        # Move to next match
        self.current_match_index = (self.current_match_index + 1) % len(self.matches)
        
        # Highlight new current match
        start, end = self.matches[self.current_match_index]
        self.text_widget.tag_remove("match", start, end)
        self.text_widget.tag_add("current_match", start, end)
        self.text_widget.see(start)
        self.text_widget.config(state=tk.DISABLED)

        self.search_status_var.set(f"{self.current_match_index + 1}/{len(self.matches)}")


class TimelineWindow(tk.Toplevel):
    """
    A window to display a master-detail view of variable change events.
    """
    def __init__(self, parent, timeline_df: pd.DataFrame, summary_data: Dict[str, Any]):
        super().__init__(parent)
        
        self.summary_data = summary_data
        profile_name = self.summary_data.get("profile_name", "Unknown Profile")

        self.title(f"Change Event Analysis for: {profile_name}")

        self.is_fullscreen = True
        self.attributes('-fullscreen', True)
        self.bind("<Escape>", self._close_window)
        self.grab_set()

        self.all_change_events: List[Dict[str, Any]] = [] 
        self.filter_var = tk.StringVar(value="ALL")

        self._process_data_for_event_table(timeline_df)
        self._create_widgets()
        self._populate_filter_combobox()
        self._populate_event_table()

    def _process_data_for_event_table(self, df: pd.DataFrame):
        self.all_change_events = []
        if df.empty:
            return

        for var_name, group in df.groupby('variable_name'):
            sorted_group = group.sort_values(by='timestamp').reset_index()
            for i in range(1, len(sorted_group)):
                prev_row = sorted_group.iloc[i-1]
                curr_row = sorted_group.iloc[i]

                if prev_row['value'] != curr_row['value']:
                    event = {
                        "timestamp": curr_row['timestamp'],
                        "variable_name": var_name,
                        "previous_value": prev_row['value'],
                        "new_value": curr_row['value']
                    }
                    self.all_change_events.append(event)
        
        self.all_change_events.sort(key=lambda x: x['timestamp'])

    def _create_widgets(self):
        main_frame = ttk.Frame(self, padding="10")
        main_frame.pack(expand=True, fill=tk.BOTH)
        main_frame.rowconfigure(2, weight=1) # Main content (paned window) will expand
        main_frame.columnconfigure(0, weight=1)

        # --- Header Frame ---
        header_frame = ttk.Frame(main_frame)
        header_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5))
        header_frame.columnconfigure(0, weight=1)
        profile_name = self.summary_data.get("profile_name", "Unknown Profile")
        run_start_time = self.summary_data.get("start_time", "Unknown Time")
        title_label = ttk.Label(
            header_frame, 
            text=f"Profile: '{profile_name}' (Run: {run_start_time})", 
            font=("TkDefaultFont", 14, "bold")
        )
        title_label.grid(row=0, column=0, sticky="w")
        button_group = ttk.Frame(header_frame)
        button_group.grid(row=0, column=1, sticky="e")
        toggle_fullscreen_button = ttk.Button(
            button_group, text="Toggle Fullscreen", command=self._toggle_fullscreen
        )
        toggle_fullscreen_button.pack(side=tk.LEFT, padx=5)
        close_button = ttk.Button(button_group, text="Close", command=self._close_window)
        close_button.pack(side=tk.LEFT, padx=5)

        # --- Breakpoint Status Frame ---
        bp_status_frame = ttk.LabelFrame(main_frame, text="Breakpoint Status", padding=5)
        bp_status_frame.grid(row=1, column=0, sticky="ew", pady=5)
        
        actions = self.summary_data.get('actions_summary', [])
        for i, action in enumerate(actions):
            spec = action.get("breakpoint_spec", "N/A")
            status = action.get("status", "Unknown")
            
            if "Error" in status:
                color = "red"
            elif "Completed" in status:
                color = "green"
            else:
                color = "orange" # For pending or other states
            
            bp_label = ttk.Label(bp_status_frame, text=f"● {spec}", foreground=color)
            bp_label.grid(row=0, column=i, sticky="w", padx=10)
            Tooltip(bp_label, text=status)

        # --- Paned Window for Master-Detail (Left/Right) ---
        paned_window = ttk.PanedWindow(main_frame, orient=tk.HORIZONTAL)
        paned_window.grid(row=2, column=0, sticky="nsew")

        # --- Left Pane (Master): Event Table & Filter ---
        master_frame = ttk.Frame(paned_window, padding="5")
        paned_window.add(master_frame, weight=1)
        master_frame.rowconfigure(1, weight=1)
        master_frame.columnconfigure(0, weight=1)

        filter_frame = ttk.Frame(master_frame)
        filter_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5))
        filter_frame.columnconfigure(1, weight=1)
        ttk.Label(filter_frame, text="Filter by Variable:").grid(row=0, column=0, sticky="w", padx=(0,5))
        self.filter_combo = ttk.Combobox(filter_frame, textvariable=self.filter_var, state="readonly")
        self.filter_combo.grid(row=0, column=1, sticky="ew")
        self.filter_combo.bind("<<ComboboxSelected>>", self._on_filter_selected)

        table_container = ttk.LabelFrame(master_frame, text="Change Events")
        table_container.grid(row=1, column=0, sticky="nsew")
        table_container.rowconfigure(0, weight=1)
        table_container.columnconfigure(0, weight=1)

        self.event_tree = ttk.Treeview(
            table_container, columns=("timestamp", "variable"), show="headings", selectmode="browse"
        )
        self.event_tree.grid(row=0, column=0, sticky="nsew")
        self.event_tree.heading("timestamp", text="Timestamp")
        self.event_tree.heading("variable", text="Variable Name")
        self.event_tree.column("timestamp", width=180, stretch=False)
        self.event_tree.column("variable", width=200, stretch=True)
        tree_scroll = ttk.Scrollbar(table_container, orient="vertical", command=self.event_tree.yview)
        tree_scroll.grid(row=0, column=1, sticky="ns")
        self.event_tree.configure(yscrollcommand=tree_scroll.set)
        self.event_tree.bind("<<TreeviewSelect>>", self._on_event_selected)
        self.event_tree.bind("<Button-3>", self._show_context_menu)

        # --- Right Pane (Detail): Diff View ---
        detail_frame = ttk.LabelFrame(paned_window, text="Value Diff", padding="5")
        paned_window.add(detail_frame, weight=3)
        detail_frame.rowconfigure(0, weight=1)
        detail_frame.columnconfigure(0, weight=1)

        self.diff_text = tk.Text(detail_frame, wrap=tk.NONE, state=tk.DISABLED, font=("Consolas", 10))
        self.diff_text.grid(row=0, column=0, sticky="nsew")
        diff_v_scroll = ttk.Scrollbar(detail_frame, orient="vertical", command=self.diff_text.yview)
        diff_v_scroll.grid(row=0, column=1, sticky="ns")
        diff_h_scroll = ttk.Scrollbar(detail_frame, orient="horizontal", command=self.diff_text.xview)
        diff_h_scroll.grid(row=1, column=0, sticky="ew")
        self.diff_text.configure(yscrollcommand=diff_v_scroll.set, xscrollcommand=diff_h_scroll.set)

        self.diff_text.tag_configure("added", foreground="#008800")
        self.diff_text.tag_configure("removed", foreground="#CC0000")
        self.diff_text.tag_configure("header", foreground="blue", font=("Consolas", 10, "bold"))

        # --- Context Menu ---
        self.context_menu = tk.Menu(self, tearoff=0)
        self.context_menu.add_command(label="View Full Value at this Timestamp", command=self._view_full_value)

    def _populate_filter_combobox(self):
        variable_names = sorted(list(set(event['variable_name'] for event in self.all_change_events)))
        self.filter_combo['values'] = ["ALL"] + variable_names

    def _on_filter_selected(self, event=None):
        self._populate_event_table()

    def _populate_event_table(self):
        for item in self.event_tree.get_children():
            self.event_tree.delete(item)

        filter_value = self.filter_var.get()
        
        events_to_show = self.all_change_events
        if filter_value != "ALL":
            events_to_show = [e for e in self.all_change_events if e['variable_name'] == filter_value]

        for i, event in enumerate(events_to_show):
            ts = event['timestamp'].strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
            var_name = event['variable_name']
            # Use the original index from all_change_events as the IID
            original_index = self.all_change_events.index(event)
            self.event_tree.insert("", tk.END, iid=str(original_index), values=(ts, var_name))

    def _on_event_selected(self, event=None):
        selection = self.event_tree.selection()
        if not selection:
            self.diff_text.config(state=tk.NORMAL)
            self.diff_text.delete('1.0', tk.END)
            self.diff_text.config(state=tk.DISABLED)
            return

        event_index = int(selection[0])
        event_data = self.all_change_events[event_index]
        prev_val_str = event_data['previous_value']
        new_val_str = event_data['new_value']

        self.diff_text.config(state=tk.NORMAL)
        self.diff_text.delete('1.0', tk.END)

        try:
            prev_pretty = json.dumps(json.loads(prev_val_str), indent=4).splitlines()
            new_pretty = json.dumps(json.loads(new_val_str), indent=4).splitlines()
            diff = difflib.unified_diff(prev_pretty, new_pretty, lineterm='', fromfile='Previous', tofile='New')

            for line in diff:
                if line.startswith('+') and not line.startswith('+++'):
                    self.diff_text.insert(tk.END, line + '\n', "added")
                elif line.startswith('-') and not line.startswith('---'):
                    self.diff_text.insert(tk.END, line + '\n', "removed")
                elif line.startswith('@@'):
                    self.diff_text.insert(tk.END, line + '\n', "header")
                else:
                    self.diff_text.insert(tk.END, line + '\n')
        except (json.JSONDecodeError, TypeError):
            self.diff_text.insert(tk.END, "--- PREVIOUS VALUE ---\n", "header")
            self.diff_text.insert(tk.END, prev_val_str + "\n\n")
            self.diff_text.insert(tk.END, "--- NEW VALUE ---\n", "header")
            self.diff_text.insert(tk.END, new_val_str + "\n")
        finally:
            self.diff_text.config(state=tk.DISABLED)

    def _show_context_menu(self, event):
        selection = self.event_tree.identify_row(event.y)
        if selection:
            self.event_tree.selection_set(selection)
            self.context_menu.post(event.x_root, event.y_root)

    def _view_full_value(self):
        selection = self.event_tree.selection()
        if not selection:
            return
        
        event_index = int(selection[0])
        event_data = self.all_change_events[event_index]
        value_str = event_data['new_value']
        var_name = event_data['variable_name']
        timestamp = event_data['timestamp'].strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]

        title = f"Full Value of '{var_name}' at {timestamp}"
        FullValueWindow(self, title=title, value_str=value_str)

    def _toggle_fullscreen(self, event=None):
        self.is_fullscreen = not self.is_fullscreen
        self.attributes("-fullscreen", self.is_fullscreen)

    def _close_window(self, event=None):
        self.destroy()