"""Unit tests for server/preflight.py (#98).

Covers the cross-repo mutation gate that the skill §Cross-repo work
protocol (active-claude-github.md, shipped via #97) requires. The
preflight is pure — it walks `related_repos` and raises
`CrossRepoMutationBlocked` for `cousin` / `foreign` targets. No
GitHub IO, no ctx dependency, so each behaviour is a fast case here.
"""

from __future__ import annotations

import pytest

from active_claude_github_mcp.models import GithubConfig
from active_claude_github_mcp.server.preflight import (
    CrossRepoMutationBlocked,
    check_cross_repo_mutation,
)


def _config_with_related(valid_config_dict, entries):
    valid_config_dict["related_repos"] = entries
    return GithubConfig.model_validate(valid_config_dict)


class TestAllowedRelationships:
    # `subsystem`, `publish_target`, `sibling`, `neighbor`, and the
    # deprecated `contribution_target` pass through silently.

    def test_subsystem_allowed(self, valid_config_dict):
        cfg = _config_with_related(
            valid_config_dict,
            [{"repo": "own-org/mcp-dev", "relationship": "subsystem"}],
        )
        check_cross_repo_mutation(cfg, "own-org", "mcp-dev")  # no raise

    def test_publish_target_allowed(self, valid_config_dict):
        cfg = _config_with_related(
            valid_config_dict,
            [{"repo": "own-org/public-mirror", "relationship": "publish_target"}],
        )
        check_cross_repo_mutation(cfg, "own-org", "public-mirror")

    def test_sibling_allowed(self, valid_config_dict):
        cfg = _config_with_related(
            valid_config_dict,
            [{"repo": "own-org/peer-project", "relationship": "sibling"}],
        )
        check_cross_repo_mutation(cfg, "own-org", "peer-project")

    def test_neighbor_allowed(self, valid_config_dict):
        cfg = _config_with_related(
            valid_config_dict,
            [{"repo": "user/sandbox", "relationship": "neighbor"}],
        )
        check_cross_repo_mutation(cfg, "user", "sandbox")

    def test_contribution_target_still_allowed(self, valid_config_dict):
        # Parses-but-deprecated: the preflight must not block while
        # users migrate. Deprecation warning is surfaced separately
        # at config-load time (tested in test_config.py).
        cfg = _config_with_related(
            valid_config_dict,
            [{"repo": "legacy/tool", "relationship": "contribution_target"}],
        )
        check_cross_repo_mutation(cfg, "legacy", "tool")


class TestBlockedRelationships:
    def test_cousin_blocks_with_manual_step(self, valid_config_dict):
        cfg = _config_with_related(
            valid_config_dict,
            [
                {
                    "repo": "other-org/shared-tool",
                    "relationship": "cousin",
                    "description": "cross-org collaborator",
                }
            ],
        )
        with pytest.raises(CrossRepoMutationBlocked) as excinfo:
            check_cross_repo_mutation(cfg, "other-org", "shared-tool")
        exc = excinfo.value
        assert exc.relationship == "cousin"
        assert exc.target_repo == "other-org/shared-tool"
        assert "explicit user permission" in exc.manual_step
        assert "other-org/shared-tool" in exc.manual_step

    def test_foreign_blocks_with_no_write_message(self, valid_config_dict):
        cfg = _config_with_related(
            valid_config_dict,
            [{"repo": "vendor/upstream", "relationship": "foreign"}],
        )
        with pytest.raises(CrossRepoMutationBlocked) as excinfo:
            check_cross_repo_mutation(cfg, "vendor", "upstream")
        exc = excinfo.value
        assert exc.relationship == "foreign"
        assert "no write access" in exc.manual_step
        assert "vendor/upstream" in exc.manual_step

    def test_manual_step_used_as_str(self, valid_config_dict):
        cfg = _config_with_related(
            valid_config_dict,
            [{"repo": "other-org/x", "relationship": "cousin"}],
        )
        try:
            check_cross_repo_mutation(cfg, "other-org", "x")
        except CrossRepoMutationBlocked as exc:
            assert str(exc) == exc.manual_step


class TestOwnRepos:
    # Own public/private repos always pass through without consulting
    # related_repos. Protects against misconfiguration where the
    # user's own repo accidentally appears in related_repos.

    def test_own_private_repo_always_allowed(self, valid_config_dict):
        # valid_config_dict.repos.private == "telejester/claude-skills-dev"
        cfg = GithubConfig.model_validate(valid_config_dict)
        check_cross_repo_mutation(cfg, "telejester", "claude-skills-dev")

    def test_own_public_repo_always_allowed(self, valid_config_dict):
        # valid_config_dict.repos.public == "telejester/claude-skills"
        cfg = GithubConfig.model_validate(valid_config_dict)
        check_cross_repo_mutation(cfg, "telejester", "claude-skills")

    def test_own_repo_passes_even_if_misdeclared_as_cousin(
        self, valid_config_dict
    ):
        # Own public listed as cousin should still pass — own repos
        # take precedence over related_repos lookup.
        cfg = _config_with_related(
            valid_config_dict,
            [
                {
                    "repo": "telejester/claude-skills",
                    "relationship": "cousin",
                }
            ],
        )
        check_cross_repo_mutation(cfg, "telejester", "claude-skills")


class TestUnknownTargets:
    def test_unknown_target_allowed_by_default(self, valid_config_dict):
        cfg = GithubConfig.model_validate(valid_config_dict)
        # No related_repos entry: the preflight must NOT invent
        # policy — allow through. The skill protocol covers discovery.
        check_cross_repo_mutation(cfg, "nobody", "nothing")


class TestCaseInsensitiveMatching:
    def test_case_drift_in_args_still_matches_cousin(self, valid_config_dict):
        cfg = _config_with_related(
            valid_config_dict,
            [{"repo": "Other-Org/Shared", "relationship": "cousin"}],
        )
        with pytest.raises(CrossRepoMutationBlocked):
            check_cross_repo_mutation(cfg, "other-org", "shared")


if __name__ == "__main__":
    import sys

    sys.exit(pytest.main([__file__, "-v"]))
