# -*- coding: utf-8 -*-
import os
import json
import logging
import tkinter as tk
from tkinter import messagebox, ttk
import tkinter.font as tkFont
from tkinter.scrolledtext import ScrolledText
from datetime import datetime
import subprocess
from typing import Any, Dict, List, Optional

# Import the registry to find GUI elements during playback
from pymsc.gui.components.command_widgets import WIDGET_MAP

def center_window(parent: tk.Widget, window: tk.Toplevel, width: int, height: int):
    """
    Centers a Toplevel window relative to its parent.
    """
    p_x = parent.winfo_rootx()
    p_y = parent.winfo_rooty()
    p_w = parent.winfo_width()
    p_h = parent.winfo_height()
    x = p_x + (p_w - width) // 2
    y = p_y + (p_h - height) // 2
    window.geometry(f"{width}x{height}+{x}+{y}")

class JsonEditor(tk.Toplevel):
    """
    A simple JSON text editor for manual script modification.
    """
    def __init__(self, parent: tk.Widget, script_path: str):
        super().__init__(parent)
        self.title("Script Editor")
        self.script_path = script_path
        center_window(parent, self, 800, 600)
        self._create_ui()
        self.load_script()

    def _create_ui(self):
        self.editor = ScrolledText(self, wrap=tk.WORD, font=("Courier New", 10))
        self.editor.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        btn_frame = tk.Frame(self)
        btn_frame.pack(fill=tk.X, pady=5)
        
        save_btn = tk.Button(btn_frame, text="Save", command=self.save_script)
        save_btn.pack(side=tk.LEFT, padx=5)
        
        close_btn = tk.Button(btn_frame, text="Close", command=self.destroy)
        close_btn.pack(side=tk.RIGHT, padx=5)

    def load_script(self):
        try:
            with open(self.script_path, "r") as file:
                content = json.load(file)
                pretty = json.dumps(content, indent=4)
                self.editor.insert(tk.END, pretty)
        except Exception as e:
            messagebox.showerror("Error", f"Load failed: {e}")

    def save_script(self):
        try:
            raw_content = self.editor.get("1.0", tk.END).strip()
            json.loads(raw_content)  # Validation
            with open(self.script_path, "w") as file:
                file.write(raw_content)
            messagebox.showinfo("Success", "Script saved.")
        except json.JSONDecodeError:
            messagebox.showerror("Error", "Invalid JSON format.")
        except Exception as e:
            messagebox.showerror("Error", f"Save failed: {e}")

class ScriptManager:
    """
    Singleton class to handle recording and playback of GUI commands.
    """
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(ScriptManager, cls).__new__(cls)
        return cls._instance

    def __init__(self, scripts_dir: str = "scripts"):
        if hasattr(self, "_initialized"):
            return
        self._initialized = True
        self.scripts_dir = scripts_dir
        self.current_script_path = None
        self.is_recording = False
        self.is_executing = False
        self.is_paused = False
        self.commands: List[Dict] = []
        self.metadata: Dict = {}
        self.action_count = 0
        self.gui_window: Optional[tk.Toplevel] = None
        self.logger = logging.getLogger("PyMsc")
        
        if not os.path.exists(self.scripts_dir):
            os.makedirs(self.scripts_dir)

    def open_manager_gui(self, root: tk.Tk):
        """
        Creates the main Script Manager interface.
        """
        if self.gui_window and self.gui_window.winfo_exists():
            self.gui_window.deiconify()
            return

        self.gui_window = tk.Toplevel(root)
        self.gui_window.title("Script Manager")
        center_window(root, self.gui_window, 600, 700)
        self._build_ui()
        self.refresh_list()

    def _build_ui(self):
        # Recording Control Frame
        rec_frame = tk.LabelFrame(self.gui_window, text="Recording Controls")
        rec_frame.pack(fill=tk.X, padx=10, pady=5)
        
        self.start_btn = tk.Button(rec_frame, text="NEW REC", command=self.setup_new_script)
        self.start_btn.pack(side=tk.LEFT, padx=5, pady=5)
        
        self.stop_btn = tk.Button(rec_frame, text="STOP", state=tk.DISABLED, command=self.stop_recording)
        self.stop_btn.pack(side=tk.LEFT, padx=5, pady=5)
        
        # Script List Frame
        list_frame = tk.LabelFrame(self.gui_window, text="Available Scripts")
        list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        self.script_listbox = tk.Listbox(list_frame)
        self.script_listbox.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        btn_frame = tk.Frame(list_frame)
        btn_frame.pack(fill=tk.X, pady=5)
        
        self.exec_btn = tk.Button(btn_frame, text="EXECUTE", command=self.run_selected)
        self.exec_btn.pack(side=tk.LEFT, padx=5)
        
        self.edit_btn = tk.Button(btn_frame, text="EDIT", command=self.edit_selected)
        self.edit_btn.pack(side=tk.LEFT, padx=5)
        
        self.del_btn = tk.Button(btn_frame, text="DELETE", command=self.delete_selected)
        self.del_btn.pack(side=tk.LEFT, padx=5)

        # Log and Status
        log_frame = tk.LabelFrame(self.gui_window, text="Execution Log")
        log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        self.log_text = ScrolledText(log_frame, state=tk.DISABLED, height=8, font=("Helvetica", 8))
        self.log_text.pack(fill=tk.BOTH, expand=True)

    def log_event(self, message: str, level: str = "info"):
        """
        Adds a timestamped message to the internal log view.
        """
        if not self.gui_window:
            return
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.log_text.config(state=tk.NORMAL)
        self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
        self.log_text.see(tk.END)
        self.log_text.config(state=tk.DISABLED)

    def setup_new_script(self):
        """
        Dialog to initialize script metadata.
        """
        diag = tk.Toplevel(self.gui_window)
        diag.title("New Script Settings")
        center_window(self.gui_window, diag, 300, 200)
        
        tk.Label(diag, text="Script Name:").pack(pady=2)
        name_var = tk.StringVar()
        tk.Entry(diag, textvariable=name_var).pack(pady=2)
        
        def confirm():
            name = name_var.get().strip()
            if not name:
                return
            self.start_recording(name)
            diag.destroy()

        tk.Button(diag, text="Start Recording", command=confirm).pack(pady=10)

    def start_recording(self, name: str):
        self.metadata = {
            "name": name,
            "date": datetime.now().isoformat(),
            "commands": []
        }
        self.current_script_path = os.path.join(self.scripts_dir, f"{name}.json")
        self.is_recording = True
        self.action_count = 0
        self.start_btn.config(state=tk.DISABLED)
        self.stop_btn.config(state=tk.NORMAL)
        self.log_event(f"Started recording: {name}")

    def write_command(self, control_id: str, action: str, value: Any):
        """
        Hook called by widgets to record an action.
        """
        if not self.is_recording:
            return
        self.action_count += 1
        cmd = {
            "index": self.action_count,
            "control_id": control_id,
            "action": action,
            "value": value,
            "delay_ms": 1000  # Default delay for playback
        }
        self.metadata["commands"].append(cmd)
        self.log_event(f"Recorded: {control_id} -> {action}({value})")

    def stop_recording(self):
        if not self.is_recording:
            return
        with open(self.current_script_path, "w") as f:
            json.dump(self.metadata, f, indent=4)
        self.is_recording = False
        self.start_btn.config(state=tk.NORMAL)
        self.stop_btn.config(state=tk.DISABLED)
        self.log_event("Recording saved.")
        self.refresh_list()

    def refresh_list(self):
        self.script_listbox.delete(0, tk.END)
        for f in os.listdir(self.scripts_dir):
            if f.endswith(".json"):
                self.script_listbox.insert(tk.END, f)

    def run_selected(self):
        selection = self.script_listbox.curselection()
        if not selection:
            return
        filename = self.script_listbox.get(selection[0])
        path = os.path.join(self.scripts_dir, filename)
        
        try:
            with open(path, "r") as f:
                data = json.load(f)
            self.is_executing = True
            self.log_event(f"Executing script: {filename}")
            self._playback_loop(data["commands"], 0)
        except Exception as e:
            messagebox.showerror("Error", f"Playback failed: {e}")

    def _playback_loop(self, commands: List[Dict], index: int):
        """
        Recursive execution of commands using Tkinter's event loop.
        """
        if not self.is_executing or index >= len(commands):
            self.is_executing = False
            self.log_event("Execution finished.")
            return

        cmd = commands[index]
        self._execute_single_command(cmd)
        
        # Schedule next command
        delay = cmd.get("delay_ms", 1000)
        self.gui_window.after(delay, lambda: self._playback_loop(commands, index + 1))

    def _execute_single_command(self, cmd: Dict):
        """
        Finds the widget and triggers the corresponding action.
        """
        ctrl_id = cmd["control_id"]
        action = cmd["action"]
        value = cmd["value"]
        
        widget_data = WIDGET_MAP.get(ctrl_id)
        if not widget_data:
            self.log_event(f"Widget not found: {ctrl_id}", "error")
            return

        # Case 1: Complex Control from WIDGET_MAP (dict entry)
        if isinstance(widget_data, dict):
            widget = widget_data["widget"]
            var = widget_data["var"]
            
            if action == "toggle" and isinstance(widget, tk.Checkbutton):
                new_bool = (value == "on")
                var.set(new_bool)
                widget.invoke()
            elif action == "set_value":
                var.set(value)
                if isinstance(widget, ttk.Combobox):
                    widget.event_generate("<<ComboboxSelected>>")
        
        # Case 2: CommandFrame class instance
        else:
            if action == "toggle" and hasattr(widget_data, "command_var"):
                widget_data.command_var.set(value == "on")
                widget_data.on_toggle()
            elif action == "set_value" and hasattr(widget_data, "cmd_var"):
                widget_data.cmd_var.set(value)
                if hasattr(widget_data, "on_select"):
                    widget_data.on_select()
                elif hasattr(widget_data, "on_change"):
                    widget_data.on_change()

        self.log_event(f"Executed: {ctrl_id} -> {value}")

    def edit_selected(self):
        selection = self.script_listbox.curselection()
        if not selection:
            return
        filename = self.script_listbox.get(selection[0])
        path = os.path.join(self.scripts_dir, filename)
        JsonEditor(self.gui_window, path)

    def delete_selected(self):
        selection = self.script_listbox.curselection()
        if not selection:
            return
        filename = self.script_listbox.get(selection[0])
        if messagebox.askyesno("Delete", f"Delete script {filename}?"):
            os.remove(os.path.join(self.scripts_dir, filename))
            self.refresh_list()

# Global accessor
def get_script_manager() -> ScriptManager:
    return ScriptManager()