"""Tests for ci_gate.policy — gate_policy.yaml loader and path matching."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest
import yaml

if TYPE_CHECKING:
    from pathlib import Path

from scripts.helpers._config import ConfigError
from scripts.helpers._paths import REPO_ROOT
from scripts.helpers.ci_gate.policy import (
    is_gate_test_path,
    is_policy_config_path,
    load_gate_policy,
    matches_path_patterns,
)
from scripts.helpers.common.test_map_loader import is_product_source
from tests.regression.scripts.helpers.gate_policy_writer import (
    DEFAULT_CONFIG_INCLUDE,
    DEFAULT_GATE_ROOTS,
    DEFAULT_TEST_EXCLUDE,
    DEFAULT_TEST_INCLUDE,
    write_gate_policy,
)


def _write_gate_policy(
    repo: Path,
    *,
    source_roots: list[str] | None = None,
    test_include: list[str] | None = None,
    test_exclude: list[str] | None = None,
    config_include: list[str] | None = None,
    exemptions: list[dict[str, object]] | None = None,
) -> None:
    write_gate_policy(
        repo,
        roots=source_roots,
        test_include=test_include,
        test_exclude=test_exclude,
        config_include=config_include,
        source_exemptions=exemptions,
        approvers=("fangkai", "hexiaowu"),
    )


def test_load_gate_policy_reads_sources_tests_configs(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    _write_gate_policy(repo)
    policy = load_gate_policy(repo)
    assert policy.roots == DEFAULT_GATE_ROOTS
    assert policy.tests.include_patterns[0].startswith("tests/")
    assert policy.configs.include_patterns == DEFAULT_CONFIG_INCLUDE


def test_load_gate_policy_requires_roots(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    ci_dir = repo / "tests" / ".ci"
    ci_dir.mkdir(parents=True, exist_ok=True)
    policy = {
        "tests": {"include": list(DEFAULT_TEST_INCLUDE), "exclude": list(DEFAULT_TEST_EXCLUDE)},
        "configs": {"include": list(DEFAULT_CONFIG_INCLUDE), "exclude": []},
        "exemptions": {"sources": [], "tests": []},
    }
    (ci_dir / "gate_policy.yaml").write_text(yaml.dump(policy), encoding="utf-8")
    (ci_dir / "approvers.yaml").write_text(yaml.dump({"approvers": ["fangkai"]}), encoding="utf-8")
    with pytest.raises(ConfigError, match="roots"):
        load_gate_policy(repo)


def test_load_gate_policy_requires_tests(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    ci_dir = repo / "tests" / ".ci"
    ci_dir.mkdir(parents=True, exist_ok=True)
    policy = {
        "roots": list(DEFAULT_GATE_ROOTS),
        "configs": {"include": list(DEFAULT_CONFIG_INCLUDE), "exclude": []},
        "exemptions": {"sources": [], "tests": []},
    }
    (ci_dir / "gate_policy.yaml").write_text(yaml.dump(policy), encoding="utf-8")
    (ci_dir / "approvers.yaml").write_text(yaml.dump({"approvers": ["fangkai"]}), encoding="utf-8")
    with pytest.raises(ConfigError, match="tests"):
        load_gate_policy(repo)


def test_load_gate_policy_requires_configs(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    ci_dir = repo / "tests" / ".ci"
    ci_dir.mkdir(parents=True, exist_ok=True)
    policy = {
        "roots": list(DEFAULT_GATE_ROOTS),
        "tests": {"include": list(DEFAULT_TEST_INCLUDE), "exclude": list(DEFAULT_TEST_EXCLUDE)},
        "exemptions": {"sources": [], "tests": []},
    }
    (ci_dir / "gate_policy.yaml").write_text(yaml.dump(policy), encoding="utf-8")
    (ci_dir / "approvers.yaml").write_text(yaml.dump({"approvers": ["fangkai"]}), encoding="utf-8")
    with pytest.raises(ConfigError, match="configs"):
        load_gate_policy(repo)


def test_is_product_source_uses_policy_roots(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    _write_gate_policy(repo)
    policy = load_gate_policy(repo)
    assert is_product_source("cli/main.py", policy.roots) is True
    assert is_product_source("web_ui/app.py", policy.roots) is True
    assert is_product_source("tests/regression/cli/test_main.py", policy.roots) is False


def test_is_policy_config_path_uses_policy_configs(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    _write_gate_policy(repo)
    policy = load_gate_policy(repo)
    assert is_policy_config_path("pyproject.toml", policy.configs) is True
    assert is_policy_config_path("tests/regression/conftest.py", policy.configs) is True
    assert is_policy_config_path("tests/.ci/gate_policy.yaml", policy.configs) is False
    assert is_policy_config_path("cli/main.py", policy.configs) is False


def test_is_policy_config_path_build_py_not_config() -> None:
    policy = load_gate_policy(REPO_ROOT)
    assert is_policy_config_path("build.py", policy.configs) is False


def test_matches_path_patterns_respects_exclude(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    _write_gate_policy(
        repo,
        test_exclude=[
            "tests/helpers/**",
            "tests/assets/**",
            "tests/regression/nightly/**",
        ],
    )
    policy = load_gate_policy(repo)
    assert is_gate_test_path("tests/regression/cli/test_run.py", policy.discovery) is True
    assert is_gate_test_path("tests/regression/nightly/test_x.py", policy.discovery) is False
    assert matches_path_patterns("tests/regression/nightly/test_x.py", policy.tests) is False


def test_load_gate_policy_missing_file_raises(tmp_path: Path) -> None:
    repo = tmp_path / "repo"
    with pytest.raises(ConfigError, match=r"tests/\.ci/approvers\.yaml.*not found"):
        load_gate_policy(repo)