"""
WebSocket Console API - Streams log output to frontend via WebSocket.

Provides a WebSocket endpoint that broadcasts all Loguru log messages
to connected frontend clients in real-time. Supports authentication
via query parameter token and maintains a ring buffer of recent messages.
"""

import asyncio
import re
from collections import deque
from typing import Optional

from fastapi import APIRouter, Query, WebSocket, WebSocketDisconnect
from loguru import logger

router = APIRouter()

# Ring buffer size for recent log messages sent on connect
HISTORY_BUFFER_SIZE = 100

# ANSI escape code pattern for stripping color codes
ANSI_ESCAPE_PATTERN = re.compile(r'\x1b\[[0-9;]*m|\<[^>]+\>')


def _strip_markup(text: str) -> str:
    """Strip Loguru markup tags and ANSI escape codes from log text."""
    # Remove Loguru markup tags like <green>, </green>, <level>, </level>, <cyan>, etc.
    cleaned = re.sub(r'</?[a-zA-Z_]+(?:\[[^\]]*\])?\s*>', '', text)
    # Remove any remaining ANSI escape codes
    cleaned = ANSI_ESCAPE_PATTERN.sub('', cleaned)
    return cleaned


def _level_to_symbol(level: str) -> str:
    """Convert log level to a visual symbol for the console."""
    symbols = {
        'TRACE': '🔎',
        'DEBUG': '🔵',
        'INFO': '⚪',
        'SUCCESS': '✅',
        'WARNING': '🟡',
        'ERROR': '🔴',
        'CRITICAL': '🔥',
    }
    return symbols.get(level.upper(), '⚪')


class ConsoleConnectionManager:
    """Manages WebSocket connections for the log console.
    
    Maintains a set of active connections and a ring buffer of recent
    log messages. When a new client connects, the buffered history
    is sent immediately.
    """

    def __init__(self) -> None:
        self.active_connections: set[WebSocket] = set()
        self.history: deque[dict] = deque(maxlen=HISTORY_BUFFER_SIZE)
        self._log = logger.bind(name='ConsoleManager')

    async def connect(self, websocket: WebSocket) -> None:
        """Accept a new WebSocket connection and send buffered history."""
        await websocket.accept()
        self.active_connections.add(websocket)
        self._log.debug(f'Console WebSocket client connected. Total: {len(self.active_connections)}')
        
        # Send buffered history
        if self.history:
            try:
                await websocket.send_json({
                    'type': 'history',
                    'messages': list(self.history)
                })
            except Exception as exc:
                self._log.debug(f'Failed to send history to client: {exc}')

    def disconnect(self, websocket: WebSocket) -> None:
        """Remove a WebSocket connection."""
        self.active_connections.discard(websocket)
        self._log.debug(f'Console WebSocket client disconnected. Total: {len(self.active_connections)}')

    async def broadcast(self, message: dict) -> None:
        """Send a log message to all connected clients and buffer it."""
        self.history.append(message)
        
        if not self.active_connections:
            return
        
        disconnected = set()
        for connection in self.active_connections:
            try:
                await connection.send_json({
                    'type': 'log',
                    'message': message
                })
            except Exception:
                disconnected.add(connection)
        
        # Clean up disconnected clients
        for conn in disconnected:
            self.active_connections.discard(conn)


# Singleton instance
console_manager = ConsoleConnectionManager()


def loguru_console_sink(message: object) -> None:
    """Loguru sink that broadcasts log messages via WebSocket.
    
    This sink is registered with Loguru and receives all log records.
    It formats them and queues them for broadcast to connected WebSocket clients.
    """
    record = message.record
    level = record['level'].name
    
    # Format the log text (strip Loguru markup)
    log_text = _strip_markup(str(message))
    # Remove trailing newline
    log_text = log_text.rstrip('\n')
    
    log_entry = {
        'timestamp': record['time'].strftime('%Y-%m-%d %H:%M:%S.%f')[:-3],
        'level': level,
        'symbol': _level_to_symbol(level),
        'message': log_text,
    }
    
    # Schedule the async broadcast from the sync sink
    try:
        loop = asyncio.get_running_loop()
        loop.create_task(console_manager.broadcast(log_entry))
    except RuntimeError:
        # No running event loop - just buffer the message
        console_manager.history.append(log_entry)


def _validate_api_key(api_key: Optional[str]) -> bool:
    """Validate the provided API key against the configured one."""
    if not api_key:
        return False
    try:
        import optrabot.config as optrabotcfg
        config = optrabotcfg.Config._instance
        if config is None:
            return False
        configured_key = config.get('general.api_key')
        return api_key == configured_key
    except Exception:
        return False


def _validate_auth(token: Optional[str]) -> bool:
    """Validate authentication for WebSocket console access.
    
    Checks the status endpoint API key from the configuration.
    Falls back to allowing access if no API key is configured.
    """
    try:
        import optrabot.config as optrabotcfg
        config = optrabotcfg.Config._instance
        if config is None:
            # Config not loaded yet - allow during startup
            return True
        try:
            configured_key = config.get('general.api_key')
            if not configured_key:
                # No API key configured - allow access
                return True
            return token == configured_key
        except (KeyError, AttributeError):
            # No API key configured - allow access
            return True
    except Exception:
        return True


@router.websocket('/ws/console')
async def websocket_console(
    websocket: WebSocket,
    token: Optional[str] = Query(default=None)
) -> None:
    """WebSocket endpoint for streaming console log output.
    
    Authenticates via query parameter 'token' which must match
    the configured API key. Sends buffered history on connect,
    then streams new log messages in real-time.
    
    Message format (sent to client):
    - History: {"type": "history", "messages": [...]}
    - Single log: {"type": "log", "message": {"timestamp": "...", "level": "...", "symbol": "...", "message": "..."}}
    """
    if not _validate_auth(token):
        await websocket.close(code=4003, reason='Authentication failed')
        return
    
    await console_manager.connect(websocket)
    try:
        while True:
            # Keep connection alive - wait for client messages (e.g., pings)
            data = await websocket.receive_text()
            # Client can send "ping" to keep alive
            if data == 'ping':
                await websocket.send_json({'type': 'pong'})
    except WebSocketDisconnect:
        console_manager.disconnect(websocket)
    except Exception:
        console_manager.disconnect(websocket)
