"""HTTP server with SSE support for the agentboard UI."""

from __future__ import annotations

import json
import os
import threading
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
from importlib import resources
from pathlib import Path
from socketserver import ThreadingMixIn

from agentboard.board import board_path, load_board, save_board, Board


class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
    daemon_threads = True
    allow_reuse_address = True

    def handle_error(self, request, client_address):
        """Silently ignore connection resets from browsers."""
        pass


class BoardHandler(BaseHTTPRequestHandler):
    board_file: Path
    sse_clients: list
    sse_lock: threading.Lock

    def log_message(self, format, *args):
        pass

    def do_GET(self):
        if self.path == "/":
            self._serve_html()
        elif self.path == "/api/board":
            self._serve_board()
        elif self.path == "/api/events":
            self._serve_sse()
        else:
            self.send_error(404)

    def do_POST(self):
        if self.path == "/api/board":
            self._save_board()
        else:
            self.send_error(404)

    def _serve_html(self):
        try:
            html_ref = resources.files("agentboard.static").joinpath("board.html")
            html = html_ref.read_text(encoding="utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Cache-Control", "no-cache")
            self.end_headers()
            self.wfile.write(html.encode("utf-8"))
        except Exception:
            self.send_error(500, "Failed to load board.html")

    def _serve_board(self):
        try:
            board = load_board(self.board_file)
            data = json.dumps(board.to_dict()).encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Cache-Control", "no-cache")
            self.end_headers()
            self.wfile.write(data)
        except Exception as e:
            self.send_error(500, str(e))

    def _save_board(self):
        try:
            length = int(self.headers.get("Content-Length", 0))
            body = self.rfile.read(length)
            data = json.loads(body)
            board = Board.from_dict(data)
            board.updated_by = "user"
            save_board(board, self.board_file)
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(b'{"ok": true}')
        except Exception as e:
            self.send_error(400, str(e))

    def _serve_sse(self):
        self.send_response(200)
        self.send_header("Content-Type", "text/event-stream")
        self.send_header("Cache-Control", "no-cache")
        self.send_header("Connection", "keep-alive")
        self.send_header("Access-Control-Allow-Origin", "*")
        self.end_headers()

        with self.sse_lock:
            self.sse_clients.append(self.wfile)

        try:
            while True:
                time.sleep(1)
                try:
                    self.wfile.write(b": keepalive\n\n")
                    self.wfile.flush()
                except (BrokenPipeError, ConnectionResetError):
                    break
        finally:
            with self.sse_lock:
                if self.wfile in self.sse_clients:
                    self.sse_clients.remove(self.wfile)


def _broadcast_update(sse_clients: list, sse_lock: threading.Lock, data: str):
    """Send a board_update event to all connected SSE clients."""
    message = f"event: board_update\ndata: {data}\n\n".encode("utf-8")
    with sse_lock:
        dead = []
        for client in sse_clients:
            try:
                client.write(message)
                client.flush()
            except (BrokenPipeError, ConnectionResetError, OSError):
                dead.append(client)
        for client in dead:
            sse_clients.remove(client)


def _file_watcher(board_file: Path, sse_clients: list, sse_lock: threading.Lock, stop_event: threading.Event):
    """Poll the board file for changes and broadcast updates via SSE."""
    last_mtime = 0.0
    while not stop_event.is_set():
        try:
            mtime = board_file.stat().st_mtime
            if mtime > last_mtime:
                last_mtime = mtime
                board = load_board(board_file)
                data = json.dumps(board.to_dict())
                _broadcast_update(sse_clients, sse_lock, data)
        except (FileNotFoundError, json.JSONDecodeError):
            pass
        stop_event.wait(1.0)


def serve(host: str = "127.0.0.1", port: int = 5555, board_file: Path | None = None, open_browser: bool = True):
    """Start the agentboard server."""
    if board_file is None:
        board_file = board_path()

    sse_clients: list = []
    sse_lock = threading.Lock()
    stop_event = threading.Event()

    class Handler(BoardHandler):
        pass

    Handler.board_file = board_file
    Handler.sse_clients = sse_clients
    Handler.sse_lock = sse_lock

    max_attempts = 10
    for attempt in range(max_attempts):
        try:
            server = ThreadingHTTPServer((host, port), Handler)
            break
        except OSError:
            if attempt == max_attempts - 1:
                raise
            port += 1

    watcher = threading.Thread(
        target=_file_watcher,
        args=(board_file, sse_clients, sse_lock, stop_event),
        daemon=True,
    )
    watcher.start()

    url = f"http://{host}:{port}"
    print(f"Agentboard running at {url}")
    print(f"Watching {board_file}")
    print("Press Ctrl+C to stop\n")

    if open_browser:
        import webbrowser
        webbrowser.open(url)

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nShutting down...")
    finally:
        stop_event.set()
        server.shutdown()
