"""No-console launcher for the coco desktop app."""

from __future__ import annotations

import os
import socket
import subprocess
import sys
import time
from pathlib import Path


if getattr(sys, "frozen", False):
    APP_DIR = Path(sys.executable).resolve().parent
    CORE_DIR = Path(os.environ.get("coco_core_dir") or APP_DIR / "core").resolve()
else:
    APP_DIR = Path(__file__).resolve().parent
    CORE_DIR = APP_DIR
os.environ.setdefault("coco_core_dir", str(CORE_DIR))
if str(CORE_DIR) not in sys.path:
    sys.path.insert(0, str(CORE_DIR))
HOST = "127.0.0.1"
PORT = 8765
HUB_HOST = "0.0.0.0"
HUB_PORT = 8790
EMBEDDED_COMMANDS = {"serve", "desktop"}


def _ensure_runtime_dependencies() -> None:
    from coco.bootstrap import add_dependency_paths, ensure_dependencies

    add_dependency_paths()
    ensure_dependencies()


def _pythonw() -> str:
    executable = Path(sys.executable)
    if executable.name.lower() == "python.exe":
        candidate = executable.with_name("pythonw.exe")
        if candidate.exists():
            return str(candidate)
    return str(executable)


def _can_connect() -> bool:
    try:
        with socket.create_connection((HOST, PORT), timeout=0.35):
            return True
    except OSError:
        return False


def _can_connect_to(host: str, port: int) -> bool:
    try:
        with socket.create_connection((host, port), timeout=0.35):
            return True
    except OSError:
        return False


def _popen(args: list[str], stdout_name: str, stderr_name: str) -> subprocess.Popen:
    kwargs = {
        "cwd": str(APP_DIR),
        "stdin": subprocess.DEVNULL,
        "stdout": (APP_DIR / stdout_name).open("ab"),
        "stderr": (APP_DIR / stderr_name).open("ab"),
        "close_fds": True,
    }
    if os.name == "nt":
        startupinfo = subprocess.STARTUPINFO()
        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
        startupinfo.wShowWindow = 0
        kwargs["startupinfo"] = startupinfo
        kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
    return subprocess.Popen(args, **kwargs)


def _child_args(pythonw: str, command: str, *args: str) -> list[str]:
    if getattr(sys, "frozen", False):
        return [str(Path(sys.executable).resolve()), command, *args]
    return [pythonw, str(CORE_DIR / "cli.py"), command, *args]


def _ensure_bridge(pythonw: str) -> subprocess.Popen | None:
    if _can_connect():
        return None

    bridge_process = _popen(
        _child_args(
            pythonw,
            "serve",
            "--host",
            HOST,
            "--port",
            str(PORT),
        ),
        "bridge.out.log",
        "bridge.err.log",
    )

    for _ in range(20):
        if _can_connect():
            return bridge_process
        time.sleep(0.1)
    return bridge_process


def _ensure_hardware_hub(pythonw: str) -> subprocess.Popen | None:
    if _can_connect_to("127.0.0.1", HUB_PORT):
        return None

    script = CORE_DIR / "panel" / "hub.py"
    if not script.exists():
        return None

    hub_process = _popen(
        [
            pythonw,
            "-u",
            str(script),
            "--host",
            HUB_HOST,
            "--port",
            str(HUB_PORT),
        ],
        "hardware-hub.out.log",
        "hardware-hub.err.log",
    )
    for _ in range(20):
        if _can_connect_to("127.0.0.1", HUB_PORT):
            return hub_process
        time.sleep(0.1)
    return hub_process


def _terminate_process(process: subprocess.Popen | None) -> None:
    if process is None or process.poll() is not None:
        return

    process.terminate()
    try:
        process.wait(timeout=3)
        return
    except subprocess.TimeoutExpired:
        pass

    process.kill()
    try:
        process.wait(timeout=3)
    except subprocess.TimeoutExpired:
        pass


def main() -> None:
    if getattr(sys, "frozen", False) and len(sys.argv) > 1 and sys.argv[1] in EMBEDDED_COMMANDS:
        _ensure_runtime_dependencies()
        from cli import main as cli_main

        cli_main()
        return

    _ensure_runtime_dependencies()
    from coco.runtime import load_or_create_bridge_token

    os.environ.setdefault("coco_bridge_token", load_or_create_bridge_token())
    os.environ.setdefault("coco_bridge_url", f"http://{HOST}:{PORT}")
    pythonw = _pythonw()
    bridge_process = _ensure_bridge(pythonw)
    hub_process = _ensure_hardware_hub(pythonw)
    desktop_process = _popen(
        _child_args(
            pythonw,
            "desktop",
            "--width",
            "540",
            "--height",
            "700",
        ),
        "desktop.out.log",
        "desktop.err.log",
    )
    try:
        desktop_process.wait()
    finally:
        _terminate_process(hub_process)
        _terminate_process(bridge_process)


if __name__ == "__main__":
    main()
