"""
Schema validation tests

Tests service-specific schema loading and policy validation
using Overwatch (Guardian) and Palisade schemas.
"""

import pytest
from highflame_policy import (
    PolicyEngine,
    new_entity_uid,
    schemas,
)


class TestSchemaExports:
    """Test that service-specific schemas are exported correctly"""

    def test_overwatch_schema_exists(self):
        """Overwatch schema should be accessible"""
        assert schemas.overwatch_schema
        assert len(schemas.overwatch_schema) > 0

    def test_palisade_schema_exists(self):
        """Palisade schema should be accessible"""
        assert schemas.palisade_schema
        assert len(schemas.palisade_schema) > 0

    def test_overwatch_context_exists(self):
        """Overwatch context metadata should be accessible"""
        assert schemas.overwatch_context
        assert len(schemas.overwatch_context) > 0

    def test_palisade_context_exists(self):
        """Palisade context metadata should be accessible"""
        assert schemas.palisade_context
        assert len(schemas.palisade_context) > 0

    def test_overwatch_schema_contains_namespace(self):
        """Overwatch schema should contain namespace declaration"""
        assert "namespace" in schemas.overwatch_schema
        assert "Overwatch" in schemas.overwatch_schema

    def test_palisade_schema_contains_namespace(self):
        """Palisade schema should contain namespace declaration"""
        assert "namespace" in schemas.palisade_schema
        assert "Palisade" in schemas.palisade_schema


class TestOverwatchSchemaValidation:
    """Test Overwatch schema validation"""

    def test_valid_overwatch_policy(self):
        """Valid Overwatch policy should be accepted"""
        valid_policy = """
            permit(
                principal is Overwatch::User,
                action == Overwatch::Action::"call_tool",
                resource is Overwatch::Tool
            )
            when {
                context.threat_count < 5 &&
                context.highest_severity != "critical"
            };
        """

        # PolicyEngine validates syntax when loading policies
        engine = PolicyEngine(schema=schemas.overwatch_schema)
        try:
            engine.load_policy(valid_policy)
        except Exception as e:
            pytest.fail(f"Valid Overwatch policy should be accepted: {e}")

    def test_invalid_syntax_rejected(self):
        """Invalid Cedar syntax results in no valid policies (deny)"""
        invalid_policy = """
            permit(
                principal is Overwatch::User,
                action == Overwatch::Action::"call_tool"
                resource is Overwatch::Tool
            ;
        """

        engine = PolicyEngine(schema=schemas.overwatch_schema)
        # cedarpy logs parse errors but doesn't raise, just treats as no valid policies
        engine.load_policy(invalid_policy)

        # Should deny because no valid policies were loaded
        decision = engine.evaluate(
            principal_type="Overwatch::User",
            principal_id="test",
            action='Overwatch::Action::"call_tool"',
            resource_type="Overwatch::Tool",
            resource_id="test",
            context={},
        )

        # Invalid syntax means no valid policies loaded, so default deny
        assert decision.effect == "Deny"

    def test_valid_policy_with_multiple_context_attributes(self):
        """Policy with multiple context attributes should be valid"""
        policy = """
            permit(
                principal is Overwatch::Agent,
                action == Overwatch::Action::"process_prompt",
                resource is Overwatch::LlmPrompt
            )
            when {
                context.threat_count == 0 &&
                context.monitoring_enabled == true &&
                context.workspace_trusted == true
            };
        """

        engine = PolicyEngine(schema=schemas.overwatch_schema)
        try:
            engine.load_policy(policy)
        except Exception as e:
            pytest.fail(f"Valid multi-attribute policy should be accepted: {e}")


class TestPalisadeSchemaValidation:
    """Test Palisade schema validation"""

    def test_valid_palisade_policy(self):
        """Valid Palisade policy should be accepted"""
        valid_policy = """
            permit(
                principal is Palisade::Scanner,
                action == Palisade::Action::"scan_artifact",
                resource is Palisade::Artifact
            )
            when {
                context.environment == "production" &&
                context.artifact_format == "safetensors"
            };
        """

        engine = PolicyEngine(schema=schemas.palisade_schema)
        try:
            engine.load_policy(valid_policy)
        except Exception as e:
            pytest.fail(f"Valid Palisade policy should be accepted: {e}")

    def test_valid_forbid_policy_with_ml_context(self):
        """Forbid policy with ML-specific context should be valid"""
        policy = """
            forbid(
                principal is Palisade::Scanner,
                action == Palisade::Action::"load_model",
                resource is Palisade::Artifact
            )
            when {
                context.pickle_exec_path_detected == true ||
                context.severity == "CRITICAL"
            };
        """

        engine = PolicyEngine(schema=schemas.palisade_schema)
        try:
            engine.load_policy(policy)
        except Exception as e:
            pytest.fail(f"Valid forbid policy should be accepted: {e}")


class TestPolicyEngineWithServiceSchemas:
    """Test policy evaluation with service-specific schemas"""

    def test_evaluate_overwatch_policy_allow(self):
        """Evaluate Overwatch policy - allow when condition met"""
        policy = """
            permit(
                principal is Overwatch::User,
                action == Overwatch::Action::"call_tool",
                resource is Overwatch::Tool
            )
            when { context.threat_count < 5 };
        """

        engine = PolicyEngine(schema=schemas.overwatch_schema)
        engine.load_policy(policy)

        decision = engine.evaluate(
            principal_type="Overwatch::User",
            principal_id="mcp_client",
            action='Overwatch::Action::"call_tool"',
            resource_type="Overwatch::Tool",
            resource_id="shell",
            context={"threat_count": 3},
        )

        assert decision.effect == "Allow"

    def test_evaluate_overwatch_policy_deny(self):
        """Evaluate Overwatch policy - deny when condition fails"""
        policy = """
            permit(
                principal is Overwatch::User,
                action == Overwatch::Action::"call_tool",
                resource is Overwatch::Tool
            )
            when { context.threat_count < 5 };
        """

        engine = PolicyEngine(schema=schemas.overwatch_schema)
        engine.load_policy(policy)

        decision = engine.evaluate(
            principal_type="Overwatch::User",
            principal_id="mcp_client",
            action='Overwatch::Action::"call_tool"',
            resource_type="Overwatch::Tool",
            resource_id="shell",
            context={"threat_count": 10},  # Too many threats
        )

        assert decision.effect == "Deny"

    def test_evaluate_palisade_policy_forbid(self):
        """Evaluate Palisade policy - forbid when condition met"""
        policy = """
            forbid(
                principal is Palisade::Scanner,
                action == Palisade::Action::"load_model",
                resource is Palisade::Artifact
            )
            when {
                context.pickle_exec_path_detected == true
            };
        """

        engine = PolicyEngine(schema=schemas.palisade_schema)
        engine.load_policy(policy)

        decision = engine.evaluate(
            principal_type="Palisade::Scanner",
            principal_id="palisade",
            action='Palisade::Action::"load_model"',
            resource_type="Palisade::Artifact",
            resource_id="model.pkl",
            context={
                "environment": "production",
                "pickle_exec_path_detected": True,
                "severity": "CRITICAL",
            },
        )

        assert decision.effect == "Deny"

    def test_evaluate_palisade_policy_allow(self):
        """Evaluate Palisade policy - allow when condition false"""
        policy = """
            // First permit the action
            permit(
                principal is Palisade::Scanner,
                action == Palisade::Action::"load_model",
                resource is Palisade::Artifact
            );

            // Then forbid if pickle execution path detected
            forbid(
                principal is Palisade::Scanner,
                action == Palisade::Action::"load_model",
                resource is Palisade::Artifact
            )
            when {
                context.pickle_exec_path_detected == true
            };
        """

        engine = PolicyEngine(schema=schemas.palisade_schema)
        engine.load_policy(policy)

        decision = engine.evaluate(
            principal_type="Palisade::Scanner",
            principal_id="palisade",
            action='Palisade::Action::"load_model"',
            resource_type="Palisade::Artifact",
            resource_id="model.safetensors",
            context={
                "environment": "production",
                "pickle_exec_path_detected": False,
                "severity": "INFO",
            },
        )

        assert decision.effect == "Allow"


class TestExamplesFromRefactoringSummary:
    """Verify examples from REFACTORING_SUMMARY.md work correctly"""

    def test_guardian_plugin_example(self):
        """Guardian plugin example from documentation"""
        policy = """
            permit(
                principal is Overwatch::User,
                action == Overwatch::Action::"call_tool",
                resource is Overwatch::Tool
            )
            when {
                context.threat_count < 10 &&
                context.highest_severity != "critical"
            };
        """

        engine = PolicyEngine(schema=schemas.overwatch_schema)
        engine.load_policy(policy)

        decision = engine.evaluate(
            principal_type="Overwatch::User",
            principal_id="mcp_client",
            action='Overwatch::Action::"call_tool"',
            resource_type="Overwatch::Tool",
            resource_id="shell",
            context={
                "threat_count": 5,
                "highest_severity": "medium",
            },
        )

        assert decision.effect == "Allow"

    def test_palisade_service_example(self):
        """Palisade service example from documentation"""
        policy = """
            forbid(
                principal is Palisade::Scanner,
                action == Palisade::Action::"load_model",
                resource is Palisade::Artifact
            )
            when {
                context.environment == "production" &&
                context.pickle_exec_path_detected == true &&
                context.severity == "CRITICAL"
            };
        """

        engine = PolicyEngine(schema=schemas.palisade_schema)
        engine.load_policy(policy)

        decision = engine.evaluate(
            principal_type="Palisade::Scanner",
            principal_id="palisade",
            action='Palisade::Action::"load_model"',
            resource_type="Palisade::Artifact",
            resource_id="model.pkl",
            context={
                "environment": "production",
                "pickle_exec_path_detected": True,
                "severity": "CRITICAL",
            },
        )

        assert decision.effect == "Deny"
