import json
import sys
import threading
import tkinter as tk
from datetime import datetime
from tkinter import filedialog

import customtkinter as ctk


class ConsoleColors:
    """Definición de paleta de colores para los logs."""
    TEXT_DEFAULT = "#E0E0E0"
    TIMESTAMP = "#808080"
    DEBUG = "#A0A0A0"
    INFO = "#61AFEF"  # Azul claro
    SUCCESS = "#98C379"  # Verde pastel
    WARNING = "#E5C07B"  # Amarillo/Naranja
    ERROR = "#E06C75"  # Rojo suave
    CRITICAL = "#FF0000"  # Rojo intenso
    HIGHLIGHT = "#D19A66"  # Color para resaltar búsquedas


class IntegratedConsole(ctk.CTkFrame):
    """
    Un widget de consola completo con barra de herramientas,
    búsqueda y visualización de logs coloreados.
    """

    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)

        # Configuración del Grid
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)  # El textbox se expande

        # --- 1. BARRA DE HERRAMIENTAS (Toolbar) ---
        self.toolbar = ctk.CTkFrame(self, height=30, fg_color="transparent")
        self.toolbar.grid(row=0, column=0, sticky="ew", padx=2, pady=(2, 5))

        # Botón Limpiar
        self.btn_clear = ctk.CTkButton(
            self.toolbar, text="🗑 Clear", width=60, height=24,
            font=("Segoe UI", 11), fg_color="#3E3E3E", hover_color="#505050",
            command=self.clear_console
        )
        self.btn_clear.pack(side="left", padx=2)

        # Botón Copiar
        self.btn_copy = ctk.CTkButton(
            self.toolbar, text="📋 Copy", width=60, height=24,
            font=("Segoe UI", 11), fg_color="#3E3E3E", hover_color="#505050",
            command=self.copy_all
        )
        self.btn_copy.pack(side="left", padx=2)

        # Botón Guardar
        self.btn_save = ctk.CTkButton(
            self.toolbar, text="💾 Save", width=60, height=24,
            font=("Segoe UI", 11), fg_color="#3E3E3E", hover_color="#505050",
            command=self.save_to_file
        )
        self.btn_save.pack(side="left", padx=2)

        # Separador visual (Spacer)
        ctk.CTkLabel(self.toolbar, text="|", text_color="gray").pack(
            side="left", padx=5)

        # Checkbox Auto-Scroll
        self.auto_scroll_var = ctk.BooleanVar(value=True)
        self.chk_autoscroll = ctk.CTkCheckBox(
            self.toolbar, text="Auto-scroll", variable=self.auto_scroll_var,
            font=("Segoe UI", 11), width=80, height=20, checkbox_width=18, checkbox_height=18
        )
        self.chk_autoscroll.pack(side="left", padx=5)

        # Barra de Búsqueda
        self.entry_search = ctk.CTkEntry(
            self.toolbar, placeholder_text="🔍 Search...", width=120, height=24,
            font=("Segoe UI", 11)
        )
        self.entry_search.pack(side="right", padx=2)
        self.entry_search.bind("<KeyRelease>", self.on_search)

        # --- 2. ÁREA DE TEXTO (Logs) ---
        self.textbox = ctk.CTkTextbox(
            self,
            font=("Consolas", 13),  # Fuente monoespaciada para mejor lectura
            fg_color="#1E1E1E",     # Fondo oscuro tipo editor
            text_color=ConsoleColors.TEXT_DEFAULT,
            wrap="word",
            state="disabled"
        )
        self.textbox.grid(row=1, column=0, sticky="nsew", padx=2, pady=2)

        # Configuración de Tags (Colores)
        self.textbox.tag_config(
            "TIMESTAMP", foreground=ConsoleColors.TIMESTAMP)
        self.textbox.tag_config("DEBUG", foreground=ConsoleColors.DEBUG)
        self.textbox.tag_config("INFO", foreground=ConsoleColors.INFO)
        self.textbox.tag_config("SUCCESS", foreground=ConsoleColors.SUCCESS)
        self.textbox.tag_config("WARNING", foreground=ConsoleColors.WARNING)
        self.textbox.tag_config("ERROR", foreground=ConsoleColors.ERROR)
        self.textbox.tag_config("CRITICAL", foreground=ConsoleColors.CRITICAL)
        self.textbox.tag_config(
            "SEARCH_HIGHLIGHT", background="#444400", foreground="#FFFFFF")

        # Menú contextual (Click derecho)
        self.context_menu = tk.Menu(self, tearoff=0, bg="#2b2b2b", fg="white")
        self.context_menu.add_command(label="Copy All", command=self.copy_all)
        self.context_menu.add_command(
            label="Clear Console", command=self.clear_console)
        self.textbox.bind("<Button-3>", self.show_context_menu)

    def write_log(self, text, level="INFO"):
        """Método principal para escribir en la consola."""
        try:
            self.textbox.configure(state="normal")

            # Timestamp
            timestamp = datetime.now().strftime("[%H:%M:%S]")
            self.textbox.insert("end", f"{timestamp} ", "TIMESTAMP")

            # Etiqueta de Nivel (opcional, visualmente ayuda)
            self.textbox.insert("end", f"[{level.upper()}] ", level.upper())

            # Mensaje
            # Aseguramos que termine en nueva línea
            if not text.endswith("\n"):
                text += "\n"

            self.textbox.insert("end", text, level.upper())

            # Auto-scroll
            if self.auto_scroll_var.get():
                self.textbox.see("end")

            self.textbox.configure(state="disabled")
        except Exception as e:
            print(f"Error writing to console widget: {e}")

    # --- Funcionalidades de la Barra de Herramientas ---

    def clear_console(self):
        self.textbox.configure(state="normal")
        self.textbox.delete("0.0", "end")
        self.textbox.configure(state="disabled")

    def copy_all(self):
        try:
            all_text = self.textbox.get("0.0", "end")
            self.clipboard_clear()
            self.clipboard_append(all_text)
            self.update()  # Necesario para finalizar la operación de portapapeles
        except Exception:
            pass

    def save_to_file(self):
        try:
            filename = filedialog.asksaveasfilename(
                defaultextension=".txt",
                filetypes=[("Text Files", "*.txt"),
                           ("Log Files", "*.log"), ("All Files", "*.*")],
                title="Save Log File"
            )
            if filename:
                with open(filename, "w", encoding="utf-8") as f:
                    f.write(self.textbox.get("0.0", "end"))
        except Exception as e:
            self.write_log(f"Error saving file: {e}", "ERROR")

    def on_search(self, event=None):
        """Resalta texto en tiempo real."""
        search_str = self.entry_search.get()

        # Limpiar tags anteriores de búsqueda
        self.textbox.tag_remove("SEARCH_HIGHLIGHT", "1.0", "end")

        if not search_str:
            return

        # Buscar y resaltar
        start_pos = "1.0"
        while True:
            start_pos = self.textbox.search(
                search_str, start_pos, stopindex="end", nocase=True)
            if not start_pos:
                break
            end_pos = f"{start_pos}+{len(search_str)}c"
            self.textbox.tag_add("SEARCH_HIGHLIGHT", start_pos, end_pos)
            start_pos = end_pos

    def show_context_menu(self, event):
        try:
            self.context_menu.tk_popup(event.x_root, event.y_root)
        finally:
            self.context_menu.grab_release()


class StreamRedirector:
    """Redirige stdout/stderr hacia el ConsoleManager."""

    def __init__(self, console_manager, stream_type):
        self.console_manager = console_manager
        self.stream_type = stream_type
        self.original_stream = getattr(sys, stream_type)

    def write(self, message):
        if message.strip():  # Ignorar líneas vacías
            self.console_manager.enqueue_message(message, self.stream_type)

        # Mantener salida en terminal real por seguridad
        try:
            self.original_stream.write(message)
            self.original_stream.flush()
        except Exception:
            pass

    def flush(self):
        try:
            self.original_stream.flush()
        except Exception:
            pass


class ConsoleManager:
    """
    Singleton que gestiona la lógica de logs y la conexión con la GUI.
    Thread-safe.
    """
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(ConsoleManager, cls).__new__(cls)
            cls._instance.buffer = []
            cls._instance.widget_frame = None  # Referencia al Frame contenedor
            cls._instance.lock = threading.Lock()
            cls._instance.is_setup = False
        return cls._instance

    def setup_redirection(self):
        """Configura la redirección de sys.stdout y sys.stderr."""
        if not self.is_setup:
            sys.stdout = StreamRedirector(self, "stdout")
            sys.stderr = StreamRedirector(self, "stderr")
            self.is_setup = True
            print("[SYSTEM] Output redirected to Warlock Integrated Console.")

    def set_widget(self, widget_frame: IntegratedConsole):
        """Vincula el widget gráfico (IntegratedConsole) y vuelca el buffer."""
        with self.lock:
            self.widget_frame = widget_frame

            # Volcar buffer acumulado
            for msg, stream_type in self.buffer:
                self._process_and_dispatch(msg, stream_type)
            self.buffer = []

    def enqueue_message(self, message, stream_type):
        """Punto de entrada desde stdout/stderr."""
        with self.lock:
            if self.widget_frame:
                self._process_and_dispatch(message, stream_type)
            else:
                self.buffer.append((message, stream_type))

    def _process_and_dispatch(self, message, stream_type="info"):
        """Determina el nivel del log y lo envía a la GUI."""
        try:
            tag = "INFO"
            msg_lower = message.lower()

            # Lógica de detección de nivel automática
            if stream_type == "stderr":
                tag = "ERROR"
            elif "error" in msg_lower or "exception" in msg_lower or "failed" in msg_lower:
                tag = "ERROR"
            elif "warning" in msg_lower:
                tag = "WARNING"
            elif "success" in msg_lower or "completed" in msg_lower:
                tag = "SUCCESS"
            elif "debug" in msg_lower:
                tag = "DEBUG"
            elif "critical" in msg_lower:
                tag = "CRITICAL"

            # Enviar al hilo principal
            self.widget_frame.after(
                0, lambda m=message, t=tag: self.widget_frame.write_log(m, t))
        except Exception:
            pass

    def write_log(self, message, tag="INFO"):
        """
        Método público para logs manuales.
        Uso: console.write_log("Proceso terminado", "SUCCESS")
        """
        with self.lock:
            if self.widget_frame:
                try:
                    self.widget_frame.after(
                        0, lambda m=message, t=tag: self.widget_frame.write_log(m, t))
                except Exception:
                    print(f"[{tag}] {message}")
            else:
                print(f"[{tag}] {message}")


# Instancia global
console = ConsoleManager()
