"""Install the workspace into a clean venv and open the projects page in a browser."""

from __future__ import annotations

import os
import socket
import subprocess
import sys
import time
import urllib.error
import urllib.request
from collections.abc import Mapping
from pathlib import Path

import pytest

sync_api = pytest.importorskip("playwright.sync_api")

_REPO_ROOT = Path(__file__).resolve().parents[1]
_KEPT_ENV = (
    "PATH",
    "HOME",
    "USER",
    "LOGNAME",
    "LANG",
    "LC_ALL",
    "LC_CTYPE",
    "TMPDIR",
    "TMP",
    "TEMP",
    "SYSTEMROOT",
)


def _fresh_env(overrides: Mapping[str, str] | None = None) -> dict[str, str]:
    env = {key: os.environ[key] for key in _KEPT_ENV if key in os.environ}
    if overrides:
        env.update(overrides)
    return env


def _venv_python(venv: Path) -> Path:
    if sys.platform == "win32":
        return venv / "Scripts" / "python.exe"
    return venv / "bin" / "python"


def _free_port() -> int:
    with socket.socket() as sock:
        sock.bind(("127.0.0.1", 0))
        return int(sock.getsockname()[1])


def _wait_until_ready(url: str, proc: subprocess.Popen[str], log_path: Path, timeout: float) -> None:
    deadline = time.monotonic() + timeout
    while time.monotonic() < deadline:
        if proc.poll() is not None:
            log = log_path.read_text(encoding="utf-8") if log_path.is_file() else ""
            raise RuntimeError(f"server exited with {proc.returncode}\n{log}")
        try:
            with urllib.request.urlopen(url, timeout=1) as response:
                if response.status == 200:
                    return
        except (urllib.error.URLError, TimeoutError, OSError):
            time.sleep(0.1)
    log = log_path.read_text(encoding="utf-8") if log_path.is_file() else ""
    raise TimeoutError(f"server did not become ready: {url}\n{log}")


@pytest.mark.playwright
def test_projects_page_loads_without_auth(tmp_path: Path) -> None:
    projects = tmp_path / "projects"
    repo = projects / "demo"
    repo.mkdir(parents=True)
    subprocess.run(["git", "init", "-b", "main", str(repo)], check=True, capture_output=True)

    venv = tmp_path / "venv"
    subprocess.run(
        ["uv", "sync", "--all-packages", "--frozen", "--no-dev"],
        cwd=_REPO_ROOT,
        env=_fresh_env({"UV_PROJECT_ENVIRONMENT": str(venv)}),
        check=True,
    )

    port = _free_port()
    base = f"http://127.0.0.1:{port}/"
    home = tmp_path / "home"
    home.mkdir()
    # Windows Path.home() reads USERPROFILE.
    server_env = _fresh_env({
        "HOME": str(home),
        "USERPROFILE": str(home),
        "PYGITWEB_AUTH": "0",
        "PYGITWEB_PROJECTROOT": str(projects),
        "PYGITWEB_PROJECTS_LIST": str(projects),
        "PYGITWEB_SETTINGS_CONFIG": str(tmp_path / "settings.json"),
        "PYGITWEB_AUTH_CONFIG": str(tmp_path / "auth.json"),
        "PYTHONUNBUFFERED": "1",
    })
    log_out_path = tmp_path / "server.out"
    log_err_path = tmp_path / "server.err"
    log_out = log_out_path.open("w", encoding="utf-8")
    log_err = log_err_path.open("w", encoding="utf-8")
    proc = subprocess.Popen(
        [
            str(_venv_python(venv)),
            "-m",
            "uvicorn",
            "pygitweb.main:app",
            "--host",
            "127.0.0.1",
            "--port",
            str(port),
        ],
        cwd=tmp_path,
        env=server_env,
        stdout=log_out,
        stderr=log_err,
    )
    try:
        _wait_until_ready(base, proc, log_err_path, timeout=30)
        with sync_api.sync_playwright() as playwright:
            browser = playwright.chromium.launch(headless=True)
            try:
                page = browser.new_page()
                page.goto(base, wait_until="domcontentloaded")
                sync_api.expect(page).to_have_title("PyGitWeb - Projects")
                sync_api.expect(page.get_by_role("heading", name="Projects")).to_be_visible()
                sync_api.expect(page.get_by_role("link", name="demo", exact=True)).to_be_visible()
            finally:
                browser.close()
    finally:
        if proc.poll() is None:
            proc.terminate()
            try:
                proc.wait(timeout=10)
            except subprocess.TimeoutExpired:
                proc.kill()
                proc.wait(timeout=10)
        log_out.close()
        log_err.close()