"""WebSocket server for sending HTML to connected clients."""

import asyncio
import json
import logging
from typing import Set, Optional
import threading
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
import uvicorn

from lib.last_view import compute_page_hash
from lib.speech_to_text.config import SimpleConfig

# Configure logging
logger = logging.getLogger(__name__)

class WebSocketManager:
    def __init__(self):
        self.active_connections: Set[WebSocket] = set()
        
    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.add(websocket)
        logger.info(f"Client connected. Total connections: {len(self.active_connections)}")
        print(f"🔌 Client connected. Total connections: {len(self.active_connections)}")
        
    def disconnect(self, websocket: WebSocket):
        self.active_connections.discard(websocket)
        logger.info(f"Client disconnected. Total connections: {len(self.active_connections)}")
        print(f"❌ Client disconnected. Total connections: {len(self.active_connections)}")
        
    async def send_html(
        self,
        html_content: str,
        js_content: Optional[str] = None,
        replaces: Optional[str] = None,
    ):
        """Send HTML + JS content to all connected clients."""
        if not self.active_connections:
            logger.warning("No connected clients to send HTML to")
            print("⚠️ No connected clients to send HTML to")
            return

        # Hash treści strony — ten sam, którym kluczowany jest widok po stronie serwera.
        # Klient go nie liczy: dostaje go tutaj i odsyła przy nagraniu jak identyfikator.
        page_hash = compute_page_hash(html_content, js_content)

        # Prepare JSON payload with HTML and JS.
        # `replaces` to hash strony, którą ta ma podmienić w miejscu (None = nowa strona).
        # Oba identyfikatory lecą razem: klient wie, którą stronę podmienić i jaki
        # identyfikator ma od teraz odsyłać dla tej treści.
        payload = {
            "html": html_content,
            "js": js_content or "",
            "hash": page_hash,
            "replaces": replaces,
        }

        text = json.dumps(payload, ensure_ascii=False)

        logger.info(f"Sending HTML+JS to {len(self.active_connections)} clients")
        print(f"📤 Sending HTML+JS to {len(self.active_connections)} clients")

        disconnected = set()
        for connection in self.active_connections:
            try:
                await connection.send_text(text)
            except Exception as e:
                logger.error(f"Error sending to client: {e}")
                print(f"❌ Error sending to client: {e}")
                disconnected.add(connection)

        for connection in disconnected:
            self.disconnect(connection)

        logger.info(f"HTML+JS sent successfully to {len(self.active_connections)} clients")
        print(f"✅ HTML+JS sent successfully to {len(self.active_connections)} clients")


# Global WebSocket manager
ws_manager = WebSocketManager()

# FastAPI app for WebSocket
app = FastAPI()

# Ostatni segment to PIN wpisywany w przeglądarce (www/pin-gate.js), ten sam,
# który bierze serwer uploadu — bez niego adres nie istnieje, więc obcy klient
# nie zobaczy prezentowanych widoków.
@app.websocket(f"/gui-socket/{SimpleConfig.get_string('access-pin')}")
async def websocket_endpoint(websocket: WebSocket):
    await ws_manager.connect(websocket)
    try:
        while True:
            # Keep connection alive and handle incoming messages
            await websocket.receive_text()
    except WebSocketDisconnect:
        ws_manager.disconnect(websocket)
    except Exception as e:
        logger.error(f"WebSocket error: {e}")
        ws_manager.disconnect(websocket)

def start_websocket_server():
    """Start the WebSocket server in a separate thread."""
    def run_server():
        uvicorn.run(app, host="localhost", port=8085, log_level="info")
    
    server_thread = threading.Thread(target=run_server, daemon=True)
    server_thread.start()
    logger.info("WebSocket server started on localhost:8085")
    print("🚀 WebSocket server started on localhost:8085")
    return server_thread

# Function to send HTML + JS to all connected clients
async def broadcast_html(
    html_content: str,
    js_content: Optional[str] = None,
    replaces: Optional[str] = None,
):
    """Send HTML + JS content to all connected WebSocket clients."""
    print(html_content)
    await ws_manager.send_html(html_content, js_content, replaces)

def broadcast_html_sync(
    html_content: str,
    js_content: Optional[str] = None,
    replaces: Optional[str] = None,
):
    """Synchronous wrapper for broadcasting HTML + JS."""
    try:
        loop = asyncio.get_event_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

    if loop.is_running():
        # If event loop is running, schedule the coroutine
        asyncio.create_task(broadcast_html(html_content, js_content, replaces))
    else:
        # If no event loop is running, run it
        loop.run_until_complete(broadcast_html(html_content, js_content, replaces))


# Auto-start mechanism (runs only once per process)
_server_started = False

def ensure_server_running():
    global _server_started
    if not _server_started:
        print("Starting WebSocket server...")
        start_websocket_server()
        print("WebSocket server is running on localhost:8085 (path includes the access PIN).")
        _server_started = True

# Automatically ensure server is running on import
ensure_server_running()
