"""正式镜像发布的供应链门禁合同。"""

from datetime import date
from pathlib import Path

from ruamel.yaml import YAML


ROOT = Path(__file__).resolve().parents[1]
DOCKERFILE = ROOT / "docker" / "Dockerfile"
RELEASE_WORKFLOW = ROOT / ".github" / "workflows" / "build-v3.yml"
TRIVY_IGNORE = ROOT / ".trivyignore.yaml"


def _load_workflow() -> dict:
    """以 YAML 1.2 解析正式发布工作流。"""
    yaml = YAML(typ="safe")
    return yaml.load(RELEASE_WORKFLOW.read_text(encoding="utf-8"))


def _steps_by_name(workflow: dict) -> dict[str, dict]:
    """按名称索引发布步骤，顺序仍由原列表校验。"""
    return {
        step["name"]: step
        for step in workflow["jobs"]["Docker-build"]["steps"]
        if "name" in step
    }


def test_base_image_uses_refreshable_tag_and_apt_does_not_upgrade_in_place() -> None:
    """基础镜像允许获得上游更新，构建阶段不得无边界升级整套 Debian。"""
    dockerfile = DOCKERFILE.read_text(encoding="utf-8")

    assert "FROM python:3.14.7-slim-trixie AS base" in dockerfile
    assert "python:3.14.7-slim-trixie@sha256:" not in dockerfile
    assert "apt-get upgrade" not in dockerfile
    assert "\n    util-linux \\\n" in dockerfile


def test_release_audits_locked_runtime_dependencies_before_building() -> None:
    """发布构建前必须审计带哈希的锁定运行时依赖。"""
    workflow = _load_workflow()
    steps = workflow["jobs"]["Docker-build"]["steps"]
    names = [step.get("name") for step in steps]
    audit = _steps_by_name(workflow)["Audit locked Python dependencies"]["run"]

    assert names.index("Audit locked Python dependencies") < names.index("Build amd64 candidate")
    assert "uv export --quiet --locked --no-dev --no-emit-project" in audit
    assert "pip-audit==2.10.1" in audit
    for option in ("--require-hashes", "--disable-pip", "--strict"):
        assert option in audit


def test_release_scans_both_architectures_before_registry_login_and_publish() -> None:
    """任一架构的最终漏洞扫描失败时都不得登录仓库或发布镜像。"""
    workflow = _load_workflow()
    steps = workflow["jobs"]["Docker-build"]["steps"]
    names = [step.get("name") for step in steps]
    indexed = _steps_by_name(workflow)

    expected_candidates = {
        "Build amd64 candidate": ("linux/amd64", "moviepilot-v3-candidate:linux-amd64"),
        "Build arm64 candidate": ("linux/arm64/v8", "moviepilot-v3-candidate:linux-arm64"),
    }
    for name, (platform, tag) in expected_candidates.items():
        build = indexed[name]["with"]
        assert build["platforms"] == platform
        assert build["load"] is True
        assert build["push"] is False
        assert build["tags"] == tag
        assert build["pull"] is True
        assert "no-cache-filters" not in build

    for name in (
        "Scan amd64 candidate vulnerabilities",
        "Scan arm64 candidate vulnerabilities",
    ):
        scan = indexed[name]
        assert scan["with"]["cache-dir"] == "${{ runner.temp }}/trivy"
        assert scan["uses"] == (
            "aquasecurity/trivy-action@"
            "a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8"
        )
        assert scan["with"].items() >= {
            "version": "v0.70.0",
            "scanners": "vuln",
            "vuln-type": "os,library",
            "severity": "HIGH,CRITICAL",
            "ignore-unfixed": True,
            "trivyignores": ".trivyignore.yaml",
            "exit-code": 1,
        }.items()

    last_scan = names.index("Scan arm64 candidate vulnerabilities")
    assert last_scan < names.index("Login DockerHub")
    assert last_scan < names.index("Login GitHub Container Registry")
    assert last_scan < names.index("Publish multi-architecture image")


def test_vulnerability_ignores_are_scoped_justified_and_time_bounded() -> None:
    """漏洞豁免必须限定制品范围，并保留复查期限和接受理由。"""
    yaml = YAML(typ="safe")
    vulnerabilities = yaml.load(TRIVY_IGNORE.read_text(encoding="utf-8"))["vulnerabilities"]

    for vulnerability in vulnerabilities:
        assert vulnerability["paths"]
        assert vulnerability["purls"]
        assert vulnerability["statement"]
        assert isinstance(vulnerability["expired_at"], date)


def test_publish_reuses_scanned_architecture_caches_without_refreshing_base() -> None:
    """发布构建复用已扫描候选缓存，不得在扫描后重新拉取未审计基础镜像。"""
    workflow = _load_workflow()
    publish = _steps_by_name(workflow)["Publish multi-architecture image"]["with"]

    assert workflow["on"]["workflow_dispatch"] is None
    assert publish["platforms"] == "linux/amd64\nlinux/arm64/v8\n"
    assert publish["push"] is True
    assert publish["pull"] is False
    assert "scope=moviepilot-v3-docker-amd64" in publish["cache-from"]
    assert "scope=moviepilot-v3-docker-arm64" in publish["cache-from"]
