"""Dev tunnel WebSocket client for routing bot execution to local machine."""

import json
import os
import sys
import threading
import time
from typing import Callable, Optional
from urllib.parse import urlparse

import websocket

# ANSI color codes for terminal output
_use_color = sys.stdout.isatty()
_COLOR_RESET = "\033[0m" if _use_color else ""
_COLOR_RED = "\033[91m" if _use_color else ""
_COLOR_CYAN = "\033[96m" if _use_color else ""

from .runner import BotRunner


class DevTunnel:
    """WebSocket tunnel client that connects to the server and receives bot execution requests."""

    def __init__(
        self,
        server_url: str,
        token: str,
        bot_id: str,
        project_dir: str,
        on_status: Callable[[str], None],
    ):
        self.server_url = server_url
        self.token = token
        self.bot_id = bot_id
        self.project_dir = project_dir
        self.on_status = on_status
        self.ws: Optional[websocket.WebSocketApp] = None
        self.tunnel_id: Optional[str] = None
        self.connected = False
        self._reconnect_delay = 1.0
        self._should_run = True

        # Parse server URL to get the host and scheme for bot WebSocket connections
        parsed = urlparse(server_url)
        self.server_host = parsed.netloc
        self.server_scheme = parsed.scheme

        # Create bot runner
        self.runner = BotRunner(
            project_dir=project_dir,
            on_log=self._on_bot_log,
            on_error=self._on_bot_error,
        )

    def start(self) -> None:
        """Start the tunnel connection (blocking)."""
        self._should_run = True
        while self._should_run:
            try:
                self._connect()
            except KeyboardInterrupt:
                self.stop()
                break
            except Exception as e:
                self.on_status(f"Connection error: {e}")
                if self._should_run:
                    self.on_status(
                        f"Reconnecting in {self._reconnect_delay:.0f}s..."
                    )
                    time.sleep(self._reconnect_delay)
                    self._reconnect_delay = min(self._reconnect_delay * 2, 30.0)

    def stop(self) -> None:
        """Stop the tunnel connection."""
        self._should_run = False
        self.runner.kill()
        if self.ws:
            self.ws.close()

    def reload(self) -> None:
        """Kill running bot process so next execution uses fresh code."""
        self.runner.kill()
        self.on_status("Bot process restarted (file change detected)")

    def _connect(self) -> None:
        """Establish WebSocket connection to server."""
        # Build tunnel WebSocket URL
        parsed = urlparse(self.server_url)
        ws_scheme = "wss" if parsed.scheme == "https" else "ws"
        ws_url = (
            f"{ws_scheme}://{parsed.netloc}/ws-tunnel"
            f"?token={self.token}&botId={self.bot_id}"
        )

        self.on_status(f"Connecting to {parsed.netloc}...")

        self.ws = websocket.WebSocketApp(
            ws_url,
            on_open=self._on_open,
            on_message=self._on_message,
            on_error=self._on_ws_error,
            on_close=self._on_close,
        )
        self.ws.run_forever(ping_interval=30, ping_timeout=10)

    def _on_open(self, ws) -> None:
        self.connected = True
        self._reconnect_delay = 1.0
        self.on_status("Connected! Waiting for bot execution requests...")

    def _on_message(self, ws, raw_data: str) -> None:
        try:
            message = json.loads(raw_data)
            msg_type = message.get("type", "")
            msg_id = message.get("id")
            data = message.get("data", {})

            if msg_type == "tunnel:registered":
                self.tunnel_id = data.get("tunnelId")
                bot_name = data.get("botName", "Unknown")
                self.on_status(
                    f"Tunnel active for '{bot_name}' (id: {self.tunnel_id})"
                )

            elif msg_type == "tunnel:execute":
                # Execute bot function locally
                threading.Thread(
                    target=self._handle_execute,
                    args=(msg_id, data),
                    daemon=True,
                ).start()

            elif msg_type == "tunnel:ping":
                self._send({"type": "tunnel:pong"})

        except json.JSONDecodeError:
            pass

    def _on_ws_error(self, ws, error) -> None:
        if self._should_run:
            self.on_status(f"WebSocket error: {error}")

    def _on_close(self, ws, close_status_code, close_msg) -> None:
        self.connected = False
        if close_status_code and close_status_code >= 4000:
            # Server rejected the connection
            error_msg = close_msg or "Unknown error"
            self.on_status(f"Rejected by server: {error_msg} (code {close_status_code})")
            if close_status_code in (4001, 4002, 4003):
                # Auth/permission error - don't retry
                self._should_run = False
        elif self._should_run:
            self.on_status("Disconnected")

    def _handle_execute(self, request_id: str, data: dict) -> None:
        """Handle a tunnel:execute request from the server."""
        func = data.get("func", "")
        params = data.get("params", {})
        token = data.get("token", "")
        context = data.get("context", {})

        self.on_status(f"Executing: {func}")

        try:
            success = self.runner.execute(
                func=func,
                params=params,
                token=token,
                context=context,
                server_host=self.server_host,
                server_scheme=self.server_scheme,
            )

            self._send({
                "type": "tunnel:execute:result",
                "id": request_id,
                "data": {"success": success},
            })
        except Exception as e:
            self._send({
                "type": "tunnel:execute:result",
                "id": request_id,
                "data": {"success": False, "error": str(e)},
            })

    def _on_bot_log(self, log_type: str, message: str) -> None:
        """Forward bot log to terminal and server."""
        print(f"  {_COLOR_CYAN}[BOT]{_COLOR_RESET} {message}")
        self._send_log("log", message)

    def _on_bot_error(self, log_type: str, message: str) -> None:
        """Forward bot error to terminal and server."""
        print(f"  {_COLOR_RED}[BOT ERROR]{_COLOR_RESET} {message}")
        self._send_log("error", message)

    def _send_log(self, log_type: str, message: str) -> None:
        """Send log message through tunnel to server."""
        self._send({
            "type": "tunnel:log",
            "data": {
                "type": log_type,
                "args": [message],
                "context": {
                    "botId": self.bot_id,
                },
            },
        })

    def _send(self, message: dict) -> None:
        """Send a JSON message through the WebSocket."""
        if self.ws and self.connected:
            try:
                self.ws.send(json.dumps(message))
            except Exception:
                pass
