"""ORCH-040: контейнер/агенты бегут под uid:gid хоста (1000:1000), не root.

Валидируют docker-compose.yml (Вариант 1 из ADR-001) и согласованность с
HOME, который форсит launcher. Чистые конфиг-тесты: парсят YAML и текст
launcher, без запуска docker/агентов.

См. docs/work-items/ORCH-040/{02-trz.md,03-acceptance-criteria.md,
04-test-plan.yaml} и 06-adr/ADR-001-run-agents-as-host-uid.md.
"""

from pathlib import Path

import pytest
import yaml

REPO_ROOT = Path(__file__).resolve().parents[1]
COMPOSE_PATH = REPO_ROOT / "docker-compose.yml"
LAUNCHER_PATH = REPO_ROOT / "src" / "agents" / "launcher.py"

# Сервисы, которые исполняют конвейер и обязаны бежать под uid хоста.
PIPELINE_SERVICES = ("orchestrator", "orchestrator-staging")

# Единый HOME агента (форсится launcher'ом); под ним должны лежать .ssh/.claude.
EXPECTED_HOME = "/home/slin"


@pytest.fixture(scope="module")
def compose() -> dict:
    """Распарсенный docker-compose.yml."""
    with COMPOSE_PATH.open(encoding="utf-8") as fh:
        data = yaml.safe_load(fh)
    assert "services" in data, "docker-compose.yml без секции services"
    return data


def _service(compose: dict, name: str) -> dict:
    services = compose["services"]
    assert name in services, f"сервис {name} отсутствует в docker-compose.yml"
    return services[name]


def _ssh_mount_target(service: dict) -> str:
    """Target SSH-маунта (источник .orchestrator-ssh) для сервиса."""
    for vol in service.get("volumes", []):
        # формат "src:target[:mode]"
        parts = vol.split(":")
        src = parts[0]
        if src.endswith(".orchestrator-ssh"):
            assert len(parts) >= 2, f"SSH-маунт без target: {vol}"
            return parts[1]
    raise AssertionError("SSH-маунт (.orchestrator-ssh) не найден в volumes")


# --- TC-01: user: "1000:1000" в обоих сервисах ---------------------------------
@pytest.mark.parametrize("name", PIPELINE_SERVICES)
def test_tc01_service_runs_as_host_uid(compose, name):
    """TC-01/AC-1: сервис бежит под uid:gid хоста 1000:1000, а не root."""
    service = _service(compose, name)
    assert "user" in service, f"{name}: отсутствует ключ user (нужен '1000:1000')"
    # docker допускает int или строку; нормализуем к строке.
    assert str(service["user"]) == "1000:1000", (
        f"{name}: user={service['user']!r}, ожидалось '1000:1000'"
    )


# --- TC-02: group_add сохраняет "999" (docker.sock — МИНА 1) --------------------
@pytest.mark.parametrize("name", PIPELINE_SERVICES)
def test_tc02_group_add_keeps_docker_gid(compose, name):
    """TC-02/AC-4: group_add содержит 999 (доступ к docker.sock не потерян)."""
    service = _service(compose, name)
    group_add = service.get("group_add", [])
    normalized = {str(g) for g in group_add}
    assert "999" in normalized, (
        f"{name}: group_add={group_add!r}, должен содержать '999' (docker.sock)"
    )


# --- TC-03: SSH-маунт согласован с HOME (под /home/slin, не /root) --------------
@pytest.mark.parametrize("name", PIPELINE_SERVICES)
def test_tc03_ssh_mount_under_home(compose, name):
    """TC-03/AC-5: target SSH-маунта лежит в HOME агента (/home/slin/.ssh)."""
    service = _service(compose, name)
    target = _ssh_mount_target(service)
    assert target == f"{EXPECTED_HOME}/.ssh", (
        f"{name}: SSH target={target!r}, ожидалось '{EXPECTED_HOME}/.ssh' "
        f"(не /root/.ssh — иначе рассинхрон с HOME агента)"
    )
    assert not target.startswith("/root/"), (
        f"{name}: SSH target указывает на чужой HOME (/root): {target}"
    )


# --- TC-04: HOME launcher'а совместим с SSH/claude-маунтами ---------------------
def test_tc04_launcher_home_matches_mounts(compose):
    """TC-04: HOME, форсимый launcher'ом, совпадает с базой SSH/claude-маунтов.

    Нет рассинхрона HOME vs uid: и env Popen, и git_env, и target SSH-маунта
    все указывают на /home/slin.
    """
    source = LAUNCHER_PATH.read_text(encoding="utf-8")
    # launcher форсит HOME в двух местах (env Popen и git_env).
    occurrences = source.count(f'"HOME": "{EXPECTED_HOME}"')
    assert occurrences >= 2, (
        f"launcher.py: ожидалось >=2 форсинга HOME={EXPECTED_HOME!r}, "
        f"найдено {occurrences}"
    )
    # И SSH-маунты обоих сервисов ведут в этот же HOME.
    for name in PIPELINE_SERVICES:
        target = _ssh_mount_target(_service(compose, name))
        assert target.startswith(f"{EXPECTED_HOME}/"), (
            f"{name}: SSH target {target} не под HOME агента {EXPECTED_HOME}"
        )
