from __future__ import annotations

import os
import subprocess
from pathlib import Path

import pytest

import trusted_launcher


@pytest.fixture
def target() -> tuple[str, list[str]]:
    return "ask-gpt", ["--out", "arg with spaces"]


def test_unjailed_execs_fixed_target(monkeypatch: pytest.MonkeyPatch, target) -> None:
    seen = {}
    monkeypatch.setattr(trusted_launcher, "in_tmp_jail", lambda: False)
    monkeypatch.setattr(os, "execve", lambda *args: seen.setdefault("exec", args))

    result = trusted_launcher.launch(*target)
    command = trusted_launcher._command(*target)
    assert result == seen["exec"]
    assert seen["exec"][:2] == (command[0], command)


def test_unsupported_target_fails_closed() -> None:
    with pytest.raises(SystemExit, match="unsupported target"):
        trusted_launcher.launch("python3", ["-c", "pass"])


def test_jailed_relaunch_preserves_args_and_allowlisted_environment(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path, target
) -> None:
    seen = {}
    monkeypatch.setattr(trusted_launcher, "in_tmp_jail", lambda: True)
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    monkeypatch.setattr(os, "isatty", lambda _fd: False)

    def run(argv: list[str], *, check: bool) -> subprocess.CompletedProcess[str]:
        seen["argv"] = argv
        seen["check"] = check
        seen["env"] = Path(argv[argv.index("--resume") + 1]).read_bytes()
        return subprocess.CompletedProcess(argv, 37)

    monkeypatch.setattr(subprocess, "run", run)
    monkeypatch.setenv("SOLWEBD_PORT", "9000")
    monkeypatch.setenv("LD_PRELOAD", "/evil.so")

    assert trusted_launcher.launch(*target) == 37
    argv = seen["argv"]
    assert argv[:8] == [
        "/usr/bin/systemd-run", "--user", "--wait", "--collect", "--quiet",
        "--service-type=exec", "--same-dir", "--pipe",
    ]
    assert argv[-3:] == ["ask-gpt", "--out", "arg with spaces"]
    assert b"SOLWEBD_PORT=9000\0" in seen["env"]
    assert b"LD_PRELOAD" not in seen["env"]
    assert seen["check"] is False
    assert not Path(argv[argv.index("--resume") + 1]).exists()


def test_jailed_relaunch_stages_attachments_and_cleans_up(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    first = tmp_path / "first file.zip"
    second = tmp_path / "second.txt"
    first.write_bytes(b"zip-bytes")
    second.write_bytes(b"text-bytes")
    seen = {}
    monkeypatch.setattr(trusted_launcher, "in_tmp_jail", lambda: True)
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    monkeypatch.setattr(os, "isatty", lambda _fd: False)

    def run(argv: list[str], **_kwargs) -> subprocess.CompletedProcess[str]:
        resume = argv.index("--resume")
        args = argv[resume + 3:]
        paths = [Path(args[args.index("-a") + 1]), Path(args[args.index("--attach") + 1])]
        seen["paths"] = paths
        seen["contents"] = [path.read_bytes() for path in paths]
        seen["modes"] = [path.stat().st_mode & 0o777 for path in paths]
        return subprocess.CompletedProcess(argv, 0)

    monkeypatch.setattr(subprocess, "run", run)
    monkeypatch.setenv("GPTBRIDGE_STATE_DIR", "/trusted/state")

    assert trusted_launcher.launch("ask-gpt", ["-a", str(first), "--attach", str(second)]) == 0
    assert seen["contents"] == [b"zip-bytes", b"text-bytes"]
    assert seen["modes"] == [0o600, 0o600]
    assert all(path.parent == tmp_path / ".overdeck/gptbridge/trusted-launch" for path in seen["paths"])
    assert all(not path.exists() for path in seen["paths"])


def test_jailed_relaunch_rejects_missing_attachment(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    monkeypatch.setattr(trusted_launcher, "in_tmp_jail", lambda: True)
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    with pytest.raises(SystemExit, match="attachment is not a file"):
        trusted_launcher.launch("ask-gpt", ["-a", str(tmp_path / "missing.zip")])


def test_tty_relaunch_uses_pty(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    seen = {}
    monkeypatch.setattr(trusted_launcher, "in_tmp_jail", lambda: True)
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    monkeypatch.setattr(os, "isatty", lambda _fd: True)
    monkeypatch.setattr(subprocess, "run", lambda argv, **_kwargs: seen.setdefault("run", subprocess.CompletedProcess(argv, 0)))

    assert trusted_launcher.launch("solwebd", []) == 0
    assert "--pty" in seen["run"].args


def test_recursion_fails_closed(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(trusted_launcher, "in_tmp_jail", lambda: True)
    monkeypatch.setenv(trusted_launcher.MARKER, "1")
    with pytest.raises(SystemExit, match="remained inside tmp jail"):
        trusted_launcher.launch("ask-gpt", [])


def test_systemd_failure_fails_closed(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    monkeypatch.setattr(trusted_launcher, "in_tmp_jail", lambda: True)
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    monkeypatch.setattr(os, "isatty", lambda _fd: False)
    monkeypatch.setattr(subprocess, "run", lambda *_args, **_kwargs: (_ for _ in ()).throw(FileNotFoundError("missing")))
    with pytest.raises(SystemExit, match="relaunch failed: missing"):
        trusted_launcher.launch("ask-gpt", [])


def test_jail_environment_marker_detects_missing_file(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setenv(trusted_launcher.JAIL_MARKER, "1")
    monkeypatch.setattr(Path, "exists", lambda _path: False)
    assert trusted_launcher.in_tmp_jail()


def test_resume_uses_fixed_target_and_minimal_environment(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    env_path = tmp_path / "env"
    env_path.write_bytes(b"HOME=/evil\0SOLWEBD_PORT=9000\0GPTBRIDGE_STATE_DIR=/trusted/state\0LD_PRELOAD=/evil.so\0TMPJAIL_ACTIVE=1\0")
    seen = {}
    monkeypatch.setattr(trusted_launcher, "in_tmp_jail", lambda: False)
    monkeypatch.setattr(trusted_launcher.pwd, "getpwuid", lambda _uid: type("Pw", (), {"pw_dir": "/trusted/home"})())
    monkeypatch.setattr(os, "execve", lambda *args: seen.setdefault("exec", args))

    trusted_launcher._resume([str(env_path), "ask-gpt", "--out", "two words"])

    executable, argv, env = seen["exec"]
    assert executable == str(Path(os.sys.executable).resolve())
    assert argv[-2:] == ["--out", "two words"]
    assert "-I" in argv
    assert "-s" not in argv
    assert trusted_launcher.ISOLATED_BOOTSTRAP in argv
    assert env == {
        "HOME": "/trusted/home",
        "PATH": trusted_launcher.SAFE_PATH,
        trusted_launcher.MARKER: "1",
        "SOLWEBD_PORT": "9000",
        "GPTBRIDGE_STATE_DIR": "/trusted/state",
    }
    assert not env_path.exists()


def test_both_browser_launchers_use_trusted_boundary() -> None:
    root = Path(__file__).parents[1]
    for name in ("ask-gpt", "solwebd"):
        source = (root / "bin" / name).read_text()
        assert f'trusted_launcher.py" {name}' in source
