# #region Test.CleanRelease.PolicyEngine [C:3] [TYPE Module] [SEMANTICS test,clean-release,policy,validation,classification]
# @BRIEF Tests for CleanPolicyEngine — validate_policy, classify_artifact, validate_resource_source, evaluate_candidate.
# @RELATION BINDS_TO -> [PolicyEngine]

import pytest
from unittest.mock import MagicMock

from datetime import UTC, datetime
from src.models.clean_release import CleanPolicySnapshot, ResourceSourceEntry, ResourceSourceRegistry, SourceRegistrySnapshot
from src.services.clean_release.policy_engine import CleanPolicyEngine, PolicyValidationResult, SourceValidationResult


def _make_policy(**overrides) -> CleanPolicySnapshot:
    """Create a clean policy snapshot with defaults."""
    params = {
        "id": "policy-1",
        "policy_id": "policy-1",
        "policy_version": "1.0",
        "content_json": {
            "profile": "enterprise-clean",
            "prohibited_artifact_categories": ["malware", "proprietary"],
            "required_system_categories": ["system-lib"],
            "external_source_forbidden": True,
        },
        "registry_snapshot_id": "registry-1",
        "immutable": True,
    }
    params.update(overrides)
    return CleanPolicySnapshot(**params)


def _make_registry(**overrides) -> SourceRegistrySnapshot:
    """Create a source registry snapshot with defaults."""
    params = {
        "id": "registry-1",
        "registry_id": "registry-1",
        "registry_version": "1.0",
        "allowed_hosts": ["repo.internal.local", "artifacts.internal.local"],
        "immutable": True,
    }
    params.update(overrides)
    return SourceRegistrySnapshot(**params)


def _make_resource_registry(hosts: list[str] | None = None) -> ResourceSourceRegistry:
    """Create a ResourceSourceRegistry with entries."""
    if hosts is None:
        hosts = ["internal.local", "repo.internal.local"]
    now = datetime.now(UTC)
    entries = [
        ResourceSourceEntry(source_id=f"src-{i}", host=h, protocol="https", purpose="internal", enabled=True)
        for i, h in enumerate(hosts)
    ]
    return ResourceSourceRegistry(
        registry_id="reg-res-1", name="Test Resource Registry", entries=entries,
        updated_at=now, updated_by="system",
    )


class TestValidatePolicy:
    """CleanPolicyEngine.validate_policy — policy consistency validation."""

    # #region test_validate_policy_ok [C:2] [TYPE Function]
    def test_validate_policy_ok(self):
        """Valid enterprise-clean policy → ok=True."""
        policy = _make_policy()
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.validate_policy()
        assert result.ok is True
        assert result.blocking_reasons == []
    # #endregion test_validate_policy_ok

    # #region test_validate_missing_registry_ref [C:2] [TYPE Function]
    def test_validate_missing_registry_ref(self):
        """Missing registry_snapshot_id → reason added."""
        policy = _make_policy(registry_snapshot_id="")
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.validate_policy()
        assert result.ok is False
        assert any("registry" in r.lower() for r in result.blocking_reasons)
    # #endregion test_validate_missing_registry_ref

    # #region test_validate_missing_prohibited [C:2] [TYPE Function]
    def test_validate_missing_prohibited(self):
        """Enterprise policy without prohibited categories → reason added."""
        policy = _make_policy(content_json={"profile": "enterprise-clean"})
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.validate_policy()
        assert any("prohibited" in r.lower() for r in result.blocking_reasons)
    # #endregion test_validate_missing_prohibited

    # #region test_validate_missing_external_forbidden [C:2] [TYPE Function]
    def test_validate_missing_external_forbidden(self):
        """Enterprise policy without external_source_forbidden → reason added."""
        policy = _make_policy(content_json={
            "profile": "enterprise-clean",
            "prohibited_artifact_categories": ["malware"],
        })
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.validate_policy()
        assert any("external" in r.lower() for r in result.blocking_reasons)
    # #endregion test_validate_missing_external_forbidden

    # #region test_validate_registry_ref_mismatch [C:2] [TYPE Function]
    def test_validate_registry_ref_mismatch(self):
        """Registry ref mismatch → reason added."""
        policy = _make_policy(registry_snapshot_id="reg-other")
        registry = _make_registry(id="registry-1")
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.validate_policy()
        assert any("mismatch" in r.lower() or "ref" in r.lower() for r in result.blocking_reasons)
    # #endregion test_validate_registry_ref_mismatch

    # #region test_validate_empty_allowed_hosts [C:2] [TYPE Function]
    def test_validate_empty_allowed_hosts(self):
        """Registry with no allowed hosts → reason added."""
        policy = _make_policy()
        registry = _make_registry(allowed_hosts=[])
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.validate_policy()
        assert any("host" in r.lower() for r in result.blocking_reasons)
    # #endregion test_validate_empty_allowed_hosts

    # #region test_validate_standard_profile_no_prohibited_check [C:2] [TYPE Function]
    def test_validate_standard_profile_no_prohibited_check(self):
        """Standard profile doesn't require prohibited categories."""
        policy = _make_policy(content_json={"profile": "standard"})
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.validate_policy()
        assert not any("prohibited" in r.lower() for r in result.blocking_reasons)
    # #endregion test_validate_standard_profile_no_prohibited_check


class TestClassifyArtifact:
    """CleanPolicyEngine.classify_artifact — artifact classification."""

    # #region test_classify_required_system [C:2] [TYPE Function]
    def test_classify_required_system(self):
        """Required system category → required-system."""
        policy = _make_policy()
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.classify_artifact({"category": "system-lib"})
        assert result == "required-system"
    # #endregion test_classify_required_system

    # #region test_classify_prohibited [C:2] [TYPE Function]
    def test_classify_prohibited(self):
        """Prohibited category → excluded-prohibited."""
        policy = _make_policy()
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.classify_artifact({"category": "malware"})
        assert result == "excluded-prohibited"
    # #endregion test_classify_prohibited

    # #region test_classify_allowed [C:2] [TYPE Function]
    def test_classify_allowed(self):
        """Unlisted category → allowed."""
        policy = _make_policy()
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.classify_artifact({"category": "documentation"})
        assert result == "allowed"
    # #endregion test_classify_allowed

    # #region test_classify_empty_category [C:2] [TYPE Function]
    def test_classify_empty_category(self):
        """Empty category → allowed."""
        policy = _make_policy()
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.classify_artifact({"category": ""})
        assert result == "allowed"
    # #endregion test_classify_empty_category

    # #region test_classify_from_legacy_policy [C:2] [TYPE Function]
    def test_classify_from_legacy_policy(self):
        """Policy without content_json but with direct attributes → works."""
        policy = _make_policy(content_json={})
        policy.prohibited_artifact_categories = ["bad"]
        policy.required_system_categories = ["core"]
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        assert engine.classify_artifact({"category": "bad"}) == "excluded-prohibited"
        assert engine.classify_artifact({"category": "core"}) == "required-system"
        assert engine.classify_artifact({"category": "other"}) == "allowed"
    # #endregion test_classify_from_legacy_policy


class TestValidateResourceSource:
    """CleanPolicyEngine.validate_resource_source — source endpoint validation."""

    # #region test_empty_endpoint [C:2] [TYPE Function]
    def test_empty_endpoint(self):
        """Empty endpoint → violation with blocked_release=True."""
        policy = _make_policy()
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.validate_resource_source("")
        assert result.ok is False
        assert result.violation is not None
        assert result.violation["blocked_release"] is True
    # #endregion test_empty_endpoint

    # #region test_endpoint_in_allowlist [C:2] [TYPE Function]
    def test_endpoint_in_allowlist(self):
        """Endpoint in allowed_hosts → ok=True."""
        policy = _make_policy()
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.validate_resource_source("repo.internal.local")
        assert result.ok is True
        assert result.violation is None
    # #endregion test_endpoint_in_allowlist

    # #region test_endpoint_not_in_allowlist [C:2] [TYPE Function]
    def test_endpoint_not_in_allowlist(self):
        """Endpoint outside allowlist → violation."""
        policy = _make_policy()
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.validate_resource_source("external.bad.com")
        assert result.ok is False
        assert result.violation["location"] == "external.bad.com"
    # #endregion test_endpoint_not_in_allowlist

    # #region test_endpoint_case_insensitive_matching [C:2] [TYPE Function]
    def test_endpoint_case_insensitive_matching(self):
        """Endpoint matching is case-insensitive — endpoint is lowered before comparison."""
        policy = _make_policy()
        registry = _make_registry(allowed_hosts=["repo.internal.local"])
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        # Different case → still found because endpoint is lowered
        result = engine.validate_resource_source("REPO.INTERNAL.LOCAL")
        assert result.ok is True
        # Exact case → also found
        result = engine.validate_resource_source("repo.internal.local")
        assert result.ok is True
    # #endregion test_endpoint_case_insensitive_matching

    # #region test_registry_with_resource_source [C:2] [TYPE Function]
    def test_registry_with_resource_source(self):
        """Using ResourceSourceRegistry instead of SourceRegistrySnapshot."""
        registry = _make_resource_registry(hosts=["internal.local"])
        policy = _make_policy()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        result = engine.validate_resource_source("internal.local")
        assert result.ok is True
    # #endregion test_registry_with_resource_source


class TestEvaluateCandidate:
    """CleanPolicyEngine.evaluate_candidate — full candidate evaluation."""

    # #region test_evaluate_candidate_clean [C:2] [TYPE Function]
    def test_evaluate_candidate_clean(self):
        """Clean candidate with valid sources → no violations."""
        policy = _make_policy()
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        artifacts = [
            {"path": "lib/core.so", "category": "system-lib"},
            {"path": "doc/readme.md", "category": "documentation"},
        ]
        sources = ["repo.internal.local"]
        classified, violations = engine.evaluate_candidate(artifacts, sources)
        assert len(classified) == 2
        assert len(violations) == 0
        assert classified[0]["classification"] == "required-system"
        assert classified[1]["classification"] == "allowed"
    # #endregion test_evaluate_candidate_clean

    # #region test_evaluate_candidate_with_violations [C:2] [TYPE Function]
    def test_evaluate_candidate_with_violations(self):
        """Candidate with prohibited artifacts and bad sources → violations."""
        policy = _make_policy()
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        artifacts = [
            {"path": "lib/malware.exe", "category": "malware"},
            {"path": "lib/ok.so", "category": "system-lib"},
        ]
        sources = ["repo.internal.local", "external.bad.com"]
        classified, violations = engine.evaluate_candidate(artifacts, sources)
        assert len(classified) == 2
        assert len(violations) >= 1
        assert violations[0]["category"] == "data-purity"
    # #endregion test_evaluate_candidate_with_violations

    # #region test_evaluate_candidate_empty [C:2] [TYPE Function]
    def test_evaluate_candidate_empty(self):
        """Empty artifacts and sources → no violations."""
        policy = _make_policy()
        registry = _make_registry()
        engine = CleanPolicyEngine(policy=policy, registry=registry)
        classified, violations = engine.evaluate_candidate([], [])
        assert classified == []
        assert violations == []
    # #endregion test_evaluate_candidate_empty


class TestPolicyValidationResult:
    """PolicyValidationResult and SourceValidationResult dataclasses."""

    # #region test_policy_validation_result [C:2] [TYPE Function]
    def test_policy_validation_result(self):
        """PolicyValidationResult dataclass."""
        r = PolicyValidationResult(ok=True, blocking_reasons=[])
        assert r.ok is True
        assert r.blocking_reasons == []
    # #endregion test_policy_validation_result

    # #region test_source_validation_result [C:2] [TYPE Function]
    def test_source_validation_result(self):
        """SourceValidationResult dataclass."""
        r = SourceValidationResult(ok=False, violation={"code": "EXT"})
        assert r.ok is False
        assert r.violation["code"] == "EXT"
    # #endregion test_source_validation_result
