"""atashi-bridge console entrypoint."""

import asyncio
import errno
import sys

from atashi_bridge.config import BridgeConfig
from atashi_bridge.server import BridgeServer


async def _is_our_bridge(port: int, timeout: float = 2.0) -> bool:
    """Whether whatever holds ``port`` speaks our WebSocket protocol.

    Discriminates "your bridge is already running" from "something else took the
    port" — two situations that need opposite advice. Measured on a real
    collision (2026-08-10): a bridge completes the upgrade, while a plain HTTP
    server on the same port fails it outright.

    Any failure reads as "not ours", which is the safe direction: the user is
    told to go look at the port rather than being wrongly reassured their bridge
    is up when it is not.
    """
    try:
        import websockets  # noqa: PLC0415 — optional at import time, required here

        conn = await asyncio.wait_for(websockets.connect(f"ws://127.0.0.1:{port}"), timeout)
    except Exception:  # noqa: BLE001 — every failure means "not ours"
        return False
    try:
        await conn.close()
    except Exception:  # noqa: BLE001 — the probe already answered the question
        pass
    return True


def _port_busy_message(port: int, ours: bool) -> str:
    """Plain-English advice for a busy port. No stack trace, no jargon.

    Deliberately NOT an automatic fallback to another port: Atashi's browser
    page dials this exact port (``bridgeClient.ts``'s BRIDGE_URL), so a bridge
    that quietly moved would be unreachable and would look like a broken
    product. Moving the port is a two-sided change, not a bridge-side one.
    """
    if ours:
        return (
            f"Atashi bridge is already running on port {port}.\n"
            "Nothing to do — go back to Atashi and click Connect."
        )
    return (
        f"Port {port} is already in use by another program, so the bridge cannot start.\n"
        "\n"
        "Atashi connects to this exact port, so the bridge cannot just move to\n"
        "another one. Find what is using it, then try again:\n"
        "\n"
        f"  Linux / macOS   lsof -i :{port}\n"
        f"  Windows         netstat -ano | findstr :{port}\n"
    )


async def _run() -> None:
    from atashi_bridge.runner import _allowed_roots

    cfg = BridgeConfig.load()
    roots_desc = ", ".join(_allowed_roots())
    server = BridgeServer(pairing_token=cfg.pairing_token, roots_desc=roots_desc)
    try:
        async with server.serving(host="127.0.0.1", port=cfg.port):
            # Announce only AFTER the bind succeeds. Printing first meant a
            # failed start still told the user it was "listening", handed them a
            # pairing token, and only then dumped a traceback — three lies and a
            # wall of Python for what is usually just a busy port.
            print(f"Atashi bridge listening on ws://127.0.0.1:{cfg.port}", flush=True)
            print(f"Pairing token: {cfg.pairing_token}", flush=True)
            print("Or just click Connect in Atashi and approve the prompt here.", flush=True)
            await asyncio.Future()  # run forever
    except OSError as exc:
        if exc.errno != errno.EADDRINUSE:
            raise
        ours = await _is_our_bridge(cfg.port)
        print(_port_busy_message(cfg.port, ours), file=sys.stderr, flush=True)
        # Already running is not a failure — the user has what they wanted.
        raise SystemExit(0 if ours else 1) from None


def main() -> None:
    try:
        asyncio.run(_run())
    except KeyboardInterrupt:
        pass


if __name__ == "__main__":
    main()
