"""WebSocket router for embedded terminal PTY sessions."""

import asyncio
import json
import logging

from fastapi import APIRouter, WebSocket, WebSocketDisconnect

from hangar.services.terminal import TerminalService

logger = logging.getLogger(__name__)

router = APIRouter(tags=["terminal"])

# Module-level terminal service (singleton, no project_path needed)
_terminal_service = TerminalService()


def get_terminal_service() -> TerminalService:
    return _terminal_service


@router.websocket("/api/terminal/ws")
async def terminal_ws(ws: WebSocket) -> None:
    """Bidirectional WebSocket relay between xterm.js and a PTY session."""
    await ws.accept()

    # Parse initial size from query params (fallback to defaults)
    cols = int(ws.query_params.get("cols", "80"))
    rows = int(ws.query_params.get("rows", "24"))
    cwd = ws.query_params.get("cwd")

    service = get_terminal_service()
    session = service.create_session(cols=cols, rows=rows, cwd=cwd)

    logger.info("Terminal WebSocket connected (cols=%d, rows=%d)", cols, rows)

    async def read_pty() -> None:
        """Read from PTY and send to WebSocket."""
        try:
            while not session.closed:
                data = await session.read()
                if data is None:
                    break
                await ws.send_bytes(data)
        except (WebSocketDisconnect, RuntimeError):
            pass
        except Exception:
            logger.exception("PTY read error")

    async def read_ws() -> None:
        """Read from WebSocket and write to PTY."""
        try:
            while not session.closed:
                message = await ws.receive()
                if message.get("type") == "websocket.disconnect":
                    break

                # Handle text messages (JSON control messages like resize)
                if "text" in message:
                    try:
                        msg = json.loads(message["text"])
                        if msg.get("type") == "resize":
                            session.resize(msg["cols"], msg["rows"])
                            continue
                    except (json.JSONDecodeError, KeyError):
                        # Not JSON or not a control message — treat as text input
                        session.write(message["text"].encode())
                        continue

                # Handle binary messages (raw terminal input)
                if "bytes" in message:
                    session.write(message["bytes"])
        except WebSocketDisconnect:
            pass
        except Exception:
            logger.exception("WebSocket read error")

    # Run both directions concurrently
    pty_task = asyncio.create_task(read_pty())
    ws_task = asyncio.create_task(read_ws())

    try:
        # Wait for either direction to finish
        done, pending = await asyncio.wait(
            [pty_task, ws_task],
            return_when=asyncio.FIRST_COMPLETED,
        )
        for task in pending:
            task.cancel()
    finally:
        service.close_session()
        try:
            await ws.close()
        except Exception:
            pass
        logger.info("Terminal WebSocket disconnected")
