"""Floating OS-level GUI overlay for Agent Johnny 5 chat."""

from __future__ import annotations

import asyncio
import queue
import threading
import tkinter as tk
from tkinter import messagebox, scrolledtext
from typing import Any

from easyclaw_tui import __version__
from easyclaw_tui.agent.config import AgentConfig


class AsyncAgentWorker(threading.Thread):
    """Background thread running the async agent loop."""

    def __init__(
        self,
        config: AgentConfig,
        send_q: queue.Queue[str],
        result_q: queue.Queue[tuple[str, str]],
        confirm_q: queue.Queue[str],
    ) -> None:
        super().__init__(daemon=True)
        self.config = config
        self.send_q = send_q
        self.result_q = result_q
        self.confirm_q = confirm_q
        self._stop_event = threading.Event()

    def stop(self) -> None:
        self._stop_event.set()

    def run(self) -> None:
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)
        try:
            self.loop.run_until_complete(self._main_loop())
        finally:
            self.loop.close()

    async def _main_loop(self) -> None:
        from easyclaw_tui.agent.core import ForemanAgent
        from easyclaw_tui.agent.tools import mcp as mcp_tools
        from easyclaw_tui.agent.tools.confirm import set_confirm_callback

        # Init MCP tools
        if self.config.api_key:
            mcp_tools.init_mcp(self.config.api_key)

        # Set GUI-based confirmation callback
        set_confirm_callback(self._gui_confirm)

        # Create agent
        agent = ForemanAgent(self.config)
        agent.on_tool_call = self._tool_call_handler

        self.result_q.put(("system", "Agent Johnny 5 is ready. Ask me anything!"))

        while not self._stop_event.is_set():
            # Non-blocking poll for user messages
            try:
                msg = self.send_q.get_nowait()
            except queue.Empty:
                await asyncio.sleep(0.1)
                continue

            if msg is None:
                # Shutdown sentinel
                break

            if msg == "/reset":
                agent.reset()
                continue

            try:
                response = await agent.chat(msg)
                self.result_q.put(("response", response))
            except Exception as e:
                self.result_q.put(("error", str(e)))

    async def _tool_call_handler(
        self, name: str, args: dict[str, Any], result: str
    ) -> None:
        """Forward tool call info to the GUI."""
        if name == "ask_user":
            return
        elif name == "run_command":
            cmd = args.get("command", "")
            self.result_q.put(("tool", f"$ {cmd}"))
        elif name == "read_file":
            self.result_q.put(("tool", f"reading {args.get('path', '?')}"))
        elif name == "write_file":
            self.result_q.put(("tool", f"writing {args.get('path', '?')}"))
        elif name == "edit_file":
            self.result_q.put(("tool", f"editing {args.get('path', '?')}"))
        elif name.startswith("mcp_"):
            short = name.replace("mcp_", "")
            self.result_q.put(("tool", f"[mcp] {short}"))
        else:
            self.result_q.put(("tool", f"[tool] {name}"))

    async def _gui_confirm(self, question: str) -> str:
        """Ask user via tkinter messagebox (from async context)."""
        self.result_q.put(("confirm", question))
        # Poll for answer (max 5 minutes)
        for _ in range(3000):
            if self._stop_event.is_set():
                return "denied"
            try:
                answer = self.confirm_q.get_nowait()
                return answer
            except queue.Empty:
                await asyncio.sleep(0.1)
        return "denied"


class OverlayApp:
    """Floating GUI overlay for Agent Johnny 5 chat."""

    # Colors
    BG_DARK = "#1a1a2e"
    BG_INPUT = "#2a2a3e"
    AMBER = "#f59e0b"
    AMBER_DIM = "#d97706"
    CYAN = "#00d4ff"
    GRAY = "#666666"
    RED = "#ef4444"
    WHITE = "#e0e0e0"
    SYSTEM_GRAY = "#888888"

    def __init__(self, config: AgentConfig) -> None:
        self.config = config
        self.send_q: queue.Queue[str] = queue.Queue()
        self.result_q: queue.Queue[tuple[str, str]] = queue.Queue()
        self.confirm_q: queue.Queue[str] = queue.Queue()
        self._closing = False

        self._build_window()
        self._build_header()
        self._build_input()
        self._build_chat_log()
        self._setup_tags()

    def _build_window(self) -> None:
        self.root = tk.Tk()
        self.root.title("Agent Johnny 5 \u2014 EasyClaw")
        self.root.geometry("500x600")
        self.root.minsize(400, 300)
        self.root.configure(bg=self.BG_DARK)
        self.root.attributes("-topmost", True)
        self.root.protocol("WM_DELETE_WINDOW", self._on_close)

        # Try to set icon (skip silently if not available)
        try:
            self.root.iconname("EasyClaw")
        except tk.TclError:
            pass

    def _build_header(self) -> None:
        header = tk.Frame(self.root, bg=self.AMBER, height=50)
        header.pack(fill=tk.X, side=tk.TOP)
        header.pack_propagate(False)

        tk.Label(
            header,
            text=f"Agent Johnny 5  v{__version__}",
            bg=self.AMBER,
            fg=self.BG_DARK,
            font=("Consolas", 12, "bold"),
        ).pack(pady=(8, 0))

        tk.Label(
            header,
            text="Your AI Admin for OpenClaw",
            bg=self.AMBER,
            fg="#2a2a2e",
            font=("Consolas", 9),
        ).pack()

    def _build_input(self) -> None:
        input_frame = tk.Frame(self.root, bg=self.BG_INPUT)
        input_frame.pack(fill=tk.X, side=tk.BOTTOM, padx=5, pady=5)

        self.input_entry = tk.Entry(
            input_frame,
            bg=self.BG_INPUT,
            fg=self.WHITE,
            font=("Consolas", 10),
            relief=tk.FLAT,
            insertbackground=self.AMBER,
        )
        self.input_entry.pack(
            side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 5), ipady=6
        )
        self.input_entry.bind("<Return>", lambda _e: self._on_send())

        send_btn = tk.Button(
            input_frame,
            text="Send",
            bg=self.AMBER,
            fg=self.BG_DARK,
            font=("Consolas", 10, "bold"),
            relief=tk.FLAT,
            command=self._on_send,
            activebackground=self.AMBER_DIM,
            cursor="hand2",
        )
        send_btn.pack(side=tk.RIGHT, padx=(0, 5), ipady=4)

    def _build_chat_log(self) -> None:
        self.chat_log = scrolledtext.ScrolledText(
            self.root,
            wrap=tk.WORD,
            bg=self.BG_DARK,
            fg=self.WHITE,
            font=("Consolas", 10),
            state=tk.DISABLED,
            relief=tk.FLAT,
            padx=10,
            pady=10,
            insertbackground=self.AMBER,
            selectbackground=self.AMBER,
            borderwidth=0,
            highlightthickness=0,
        )
        self.chat_log.pack(fill=tk.BOTH, expand=True, padx=5, pady=(5, 0))

    def _setup_tags(self) -> None:
        self.chat_log.tag_configure(
            "user", foreground=self.CYAN, font=("Consolas", 10, "bold")
        )
        self.chat_log.tag_configure(
            "bot", foreground=self.AMBER, font=("Consolas", 10)
        )
        self.chat_log.tag_configure(
            "tool", foreground=self.GRAY, font=("Consolas", 9)
        )
        self.chat_log.tag_configure(
            "error", foreground=self.RED, font=("Consolas", 10)
        )
        self.chat_log.tag_configure(
            "system", foreground=self.SYSTEM_GRAY, font=("Consolas", 9, "italic")
        )

    def _append(self, text: str, tag: str) -> None:
        """Append text to the chat log with a tag."""
        self.chat_log.config(state=tk.NORMAL)
        self.chat_log.insert(tk.END, text, tag)
        self.chat_log.see(tk.END)
        self.chat_log.config(state=tk.DISABLED)

    def _on_send(self) -> None:
        text = self.input_entry.get().strip()
        if not text:
            return

        self.input_entry.delete(0, tk.END)

        # Handle REPL commands locally
        low = text.lower()
        if low in ("exit", "quit", "/quit"):
            self._on_close()
            return
        elif low == "/reset":
            self._append("Conversation cleared.\n\n", "system")
            # Send a reset sentinel — worker will handle it
            self.send_q.put("/reset")
            return
        elif low == "/status":
            self._show_status()
            return
        elif low == "/help":
            self._show_help()
            return

        # Show user message and send to agent
        self._append(f"You > {text}\n", "user")
        self._append("Thinking...\n", "system")
        self.send_q.put(text)

    def _show_status(self) -> None:
        lines = [
            "Agent Status",
            f"  Model:     {self.config.model}",
            f"  MCP tools: {'enabled' if self.config.api_key else 'disabled'}",
            f"  OR key:    {'set' if self.config.openrouter_key else 'not set'}",
            "",
        ]
        self._append("\n".join(lines) + "\n", "system")

    def _show_help(self) -> None:
        lines = [
            "Commands:",
            "  /reset  \u2014 clear conversation history",
            "  /status \u2014 show agent status",
            "  /help   \u2014 show this help",
            "  exit    \u2014 close overlay",
            "",
        ]
        self._append("\n".join(lines) + "\n", "system")

    def _poll_results(self) -> None:
        """Drain the result queue and update the chat log."""
        try:
            while True:
                msg_type, content = self.result_q.get_nowait()

                if msg_type == "confirm":
                    answer = messagebox.askyesno(
                        "Agent Johnny 5 \u2014 Confirmation",
                        content,
                        parent=self.root,
                    )
                    self.confirm_q.put("approved" if answer else "denied")
                elif msg_type == "response":
                    # Remove the "Thinking..." line
                    self._remove_thinking()
                    self._append(f"Johnny 5 > {content}\n\n", "bot")
                elif msg_type == "tool":
                    self._append(f"  {content}\n", "tool")
                elif msg_type == "error":
                    self._remove_thinking()
                    self._append(f"Error: {content}\n\n", "error")
                elif msg_type == "system":
                    self._append(f"{content}\n\n", "system")
        except queue.Empty:
            pass

        if not self._closing:
            self.root.after(100, self._poll_results)

    def _remove_thinking(self) -> None:
        """Remove the last 'Thinking...' line if present."""
        self.chat_log.config(state=tk.NORMAL)
        # Search backward for "Thinking..."
        pos = self.chat_log.search(
            "Thinking...", tk.END, backwards=True, stopindex="1.0"
        )
        if pos:
            line_start = f"{pos} linestart"
            line_end = f"{pos} lineend + 1 char"
            self.chat_log.delete(line_start, line_end)
        self.chat_log.config(state=tk.DISABLED)

    def _on_close(self) -> None:
        self._closing = True
        # Signal worker to stop
        self.send_q.put(None)  # type: ignore[arg-type]
        if hasattr(self, "_worker"):
            self._worker.stop()
            self._worker.join(timeout=3)
        self.root.destroy()

    def run(self) -> None:
        """Start the worker thread and enter the tkinter main loop."""
        self._worker = AsyncAgentWorker(
            self.config, self.send_q, self.result_q, self.confirm_q
        )
        self._worker.start()

        # Start polling for results
        self.root.after(100, self._poll_results)

        # Focus the input field
        self.input_entry.focus_set()

        # Enter the tkinter event loop (blocks until window is closed)
        self.root.mainloop()
