from __future__ import annotations

import os
import shutil
import subprocess
import time

import pytest


os.environ["STALL_GUARD_NOTIFY"] = "0"

PRIVATE_DISPLAY_NUMBERS = range(90, 100)
_xvfb: subprocess.Popen[bytes] | None = None


def _display_is_up(display: str) -> bool:
    return (
        subprocess.run(
            ["xdpyinfo", "-display", display],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            check=False,
        ).returncode
        == 0
    )


def _start_private_xvfb() -> tuple[subprocess.Popen[bytes], str] | None:
    """Claim a private display by starting on it; X sockets are abstract, so a
    filesystem probe of /tmp/.X11-unix misses servers that already hold one."""
    for number in PRIVATE_DISPLAY_NUMBERS:
        display = f":{number}"
        process = subprocess.Popen(
            ["Xvfb", display, "-screen", "0", "1280x1024x24", "-nolisten", "tcp"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
        deadline = time.monotonic() + 10.0
        while time.monotonic() < deadline:
            if process.poll() is not None:
                break
            if _display_is_up(display):
                return process, display
            time.sleep(0.05)
        _stop(process)
    return None


def _stop(process: subprocess.Popen[bytes]) -> None:
    if process.poll() is not None:
        return
    process.terminate()
    try:
        process.wait(timeout=5)
    except subprocess.TimeoutExpired:
        process.kill()


def pytest_configure(config: pytest.Config) -> None:
    """Keep GTK widgets off the developer's session display.

    Components fall back to a fake GTK only when a test injects one; the rest build
    real widgets, which map windows on whatever DISPLAY the shell exported.
    """
    global _xvfb
    if os.environ.get("SYSTRAY_AI_GUI_TESTS") == "1":
        display = os.environ.get("DISPLAY", "")
        allowed = {f":{number}" for number in PRIVATE_DISPLAY_NUMBERS}
        if display not in allowed:
            raise pytest.UsageError(
                f"GUI suite needs a private display from {min(PRIVATE_DISPLAY_NUMBERS)}-"
                f"{max(PRIVATE_DISPLAY_NUMBERS)}, got DISPLAY={display!r}; "
                "run it through tests/run_gui_tests.sh"
            )
        return

    if shutil.which("Xvfb") is None:
        raise pytest.UsageError("Xvfb is required so GTK tests never draw on the session display")

    started = _start_private_xvfb()
    if started is None:
        raise pytest.UsageError(
            f"no usable private X11 display in :{min(PRIVATE_DISPLAY_NUMBERS)}-"
            f":{max(PRIVATE_DISPLAY_NUMBERS)}"
        )

    _xvfb, display = started
    os.environ["DISPLAY"] = display
    os.environ.pop("WAYLAND_DISPLAY", None)


def pytest_unconfigure(config: pytest.Config) -> None:
    global _xvfb
    if _xvfb is None:
        return
    _xvfb.terminate()
    try:
        _xvfb.wait(timeout=5)
    except subprocess.TimeoutExpired:
        _xvfb.kill()
    _xvfb = None


@pytest.fixture(autouse=True)
def _no_real_gtk_unless_gui_suite(monkeypatch: pytest.MonkeyPatch) -> None:
    if os.environ.get("SYSTRAY_AI_GUI_TESTS") == "1":
        return
    monkeypatch.setattr("ui.theme._gtk_prefer_dark_theme", lambda: None)
    monkeypatch.setattr("ui.theme._gsettings_get", lambda schema, key: None)

