#!/usr/bin/env python3
"""Assistant Mode launcher: backend + voice loop + avatar UI."""

from __future__ import annotations

import argparse
import logging
import threading
import time
import webbrowser

import requests
import uvicorn
from requests.exceptions import RequestException

from app.core.config import settings
from app.main import app as fastapi_app
from app.services.ui_events import event_hub
from app.services.voice_runtime import voice_runtime
from voice_client import add_voice_cli_args, args_to_config, run_voice_client

LOGGER = logging.getLogger("sentineledge.assistant_mode")


def _build_parser() -> argparse.ArgumentParser:
    """Build Assistant Mode CLI parser."""
    parser = argparse.ArgumentParser(
        description=f"Run {settings.app_name} in Assistant Mode (backend + voice + avatar UI)",
    )
    parser.add_argument("--host", default="127.0.0.1", help="Backend bind host")
    parser.add_argument("--port", type=int, default=8000, help="Backend bind port")
    parser.add_argument("--no-browser", action="store_true", help="Skip auto-opening Assistant UI")
    parser.add_argument(
        "--ui-url",
        default=None,
        help="Optional Assistant UI URL override (default: http://localhost:<port>/assistant/ui)",
    )
    parser.add_argument(
        "--startup-timeout",
        type=float,
        default=20.0,
        help="Seconds to wait for backend readiness",
    )

    add_voice_cli_args(parser, default_server="http://127.0.0.1:8000")
    return parser


def _start_backend(host: str, port: int) -> tuple[uvicorn.Server, threading.Thread]:
    """Start uvicorn server in a background daemon thread."""
    config = uvicorn.Config(
        fastapi_app,
        host=host,
        port=port,
        log_level="info",
        reload=False,
    )
    server = uvicorn.Server(config=config)

    thread = threading.Thread(target=server.run, name="assistant-mode-backend", daemon=True)
    thread.start()
    return server, thread


def _wait_for_backend(base_url: str, timeout_seconds: float) -> bool:
    """Poll /health until backend responds or timeout is reached."""
    deadline = time.monotonic() + max(timeout_seconds, 1.0)
    endpoint = f"{base_url.rstrip('/')}/health"

    while time.monotonic() < deadline:
        try:
            response = requests.get(endpoint, timeout=1.0)
            if response.status_code == 200:
                return True
        except RequestException:
            pass
        time.sleep(0.2)

    return False


def _effective_server_url(raw_server: str, host: str, port: int) -> str:
    """Resolve voice target server URL for local backend."""
    default_raw = "http://127.0.0.1:8000"
    if raw_server == default_raw and (host not in {"127.0.0.1", "localhost"} or port != 8000):
        return f"http://127.0.0.1:{port}"
    return raw_server


def main() -> int:
    """Assistant Mode entrypoint."""
    args = _build_parser().parse_args()

    server, thread = _start_backend(host=args.host, port=args.port)
    server_url = f"http://127.0.0.1:{args.port}"

    try:
        if not _wait_for_backend(base_url=server_url, timeout_seconds=args.startup_timeout):
            LOGGER.error("Backend did not become ready within %.1f seconds", args.startup_timeout)
            return 1

        ui_url = args.ui_url or f"http://localhost:{args.port}/assistant/ui"
        if not args.no_browser:
            webbrowser.open(ui_url, new=1)

        args.server = _effective_server_url(args.server, host=args.host, port=args.port)
        voice_config = args_to_config(args)
        try:
            return run_voice_client(
                config=voice_config,
                event_callback=event_hub.broadcast,
            )
        except Exception:  # pragma: no cover - defensive runtime path
            LOGGER.exception("Voice loop crashed")
            voice_runtime.set_worker_active(False, error="voice loop crashed")
            return 1
    finally:
        server.should_exit = True
        thread.join(timeout=10)


if __name__ == "__main__":
    raise SystemExit(main())
