"""Tests for policy schema data models."""

import pytest
from datetime import timedelta

from hieromem_core.policy.schema import (
    DecayCurve,
    RetentionPolicy,
    SummarizationPolicy,
    GraphPolicy,
    GovernancePolicy,
    PriorityPolicy,
    MemoryPolicy,
)


class TestDecayCurve:
    """Tests for DecayCurve enum."""

    def test_all_values(self) -> None:
        assert DecayCurve.LINEAR.value == "linear"
        assert DecayCurve.EXPONENTIAL.value == "exponential"
        assert DecayCurve.STEP.value == "step"
        assert DecayCurve.NONE.value == "none"

    def test_from_string(self) -> None:
        assert DecayCurve("linear") == DecayCurve.LINEAR
        assert DecayCurve("exponential") == DecayCurve.EXPONENTIAL


class TestRetentionPolicy:
    """Tests for RetentionPolicy."""

    def test_defaults(self) -> None:
        policy = RetentionPolicy()
        assert policy.horizon is None
        assert policy.decay_curve == DecayCurve.EXPONENTIAL
        assert policy.base_lambda == 0.95
        assert policy.min_trust == 0.1
        assert policy.access_boost == 1.0

    def test_exponential_decay(self) -> None:
        policy = RetentionPolicy(
            decay_curve=DecayCurve.EXPONENTIAL,
            base_lambda=0.9,
        )
        # One cycle: 1.0 * 0.9 = 0.9
        assert policy.compute_decay(1.0, cycles_unused=1) == pytest.approx(0.9)
        # Two cycles: 1.0 * 0.9 * 0.9 = 0.81
        assert policy.compute_decay(1.0, cycles_unused=2) == pytest.approx(0.81)

    def test_linear_decay(self) -> None:
        policy = RetentionPolicy(
            decay_curve=DecayCurve.LINEAR,
            base_lambda=0.9,  # Decay rate = 0.1 per cycle
        )
        # One cycle: 1.0 - 0.1 = 0.9
        assert policy.compute_decay(1.0, cycles_unused=1) == pytest.approx(0.9)
        # Two cycles: 1.0 - 0.1 - 0.1 = 0.8
        assert policy.compute_decay(1.0, cycles_unused=2) == pytest.approx(0.8)

    def test_no_decay(self) -> None:
        policy = RetentionPolicy(decay_curve=DecayCurve.NONE)
        assert policy.compute_decay(0.5, cycles_unused=100) == 0.5

    def test_step_decay(self) -> None:
        policy = RetentionPolicy(
            decay_curve=DecayCurve.STEP,
            min_trust=0.3,
        )
        # High trust - no change
        assert policy.compute_decay(0.8, cycles_unused=1) == pytest.approx(0.8)
        # Near threshold (< min_trust * 2 = 0.6) - drops to 0
        assert policy.compute_decay(0.5, cycles_unused=1) == 0.0

    def test_decay_never_negative(self) -> None:
        policy = RetentionPolicy(
            decay_curve=DecayCurve.LINEAR,
            base_lambda=0.5,  # Decay 0.5 per cycle
        )
        # Should clamp at 0
        assert policy.compute_decay(0.3, cycles_unused=10) == 0.0

    def test_horizon_conversion(self) -> None:
        # Integer days converted to timedelta
        policy = RetentionPolicy()
        policy.__post_init__()  # Already done, but test explicit

        # Test with explicit integer (converted via __post_init__)
        policy2 = RetentionPolicy(horizon=30)  # type: ignore[arg-type]
        assert policy2.horizon == timedelta(days=30)


class TestSummarizationPolicy:
    """Tests for SummarizationPolicy."""

    def test_defaults(self) -> None:
        policy = SummarizationPolicy()
        assert policy.max_episodes == 100
        assert policy.max_age is None
        assert policy.depth == 2
        assert policy.preserve_graph is True
        assert policy.min_fidelity == 0.7

    def test_max_age_conversion(self) -> None:
        policy = SummarizationPolicy(max_age=7)  # type: ignore[arg-type]
        assert policy.max_age == timedelta(days=7)


class TestGraphPolicy:
    """Tests for GraphPolicy."""

    def test_defaults(self) -> None:
        policy = GraphPolicy()
        assert policy.edge_ttl is None
        assert policy.prune_orphans is True
        assert policy.max_hops == 5
        assert policy.protected_edge_types == set()

    def test_protected_edge_types_from_list(self) -> None:
        policy = GraphPolicy(protected_edge_types=["is_a", "has_prop"])  # type: ignore[arg-type]
        assert "is_a" in policy.protected_edge_types
        assert "has_prop" in policy.protected_edge_types


class TestGovernancePolicy:
    """Tests for GovernancePolicy."""

    def test_defaults(self) -> None:
        policy = GovernancePolicy()
        assert policy.forbidden_relations == set()
        assert policy.audit_required is False
        assert policy.min_lambda == 0.8
        assert policy.max_lambda == 0.99

    def test_forbidden_relation_check(self) -> None:
        policy = GovernancePolicy(forbidden_relations={"kills", "harms"})
        assert policy.is_relation_forbidden("kills") is True
        assert policy.is_relation_forbidden("KILLS") is True  # Case insensitive
        assert policy.is_relation_forbidden("helps") is False

    def test_blocked_content_patterns(self) -> None:
        policy = GovernancePolicy(
            blocked_content_patterns=["password", r"credit.?card"]
        )
        assert policy.is_content_blocked("my password is secret") is True
        assert policy.is_content_blocked("my creditcard number") is True
        assert policy.is_content_blocked("hello world") is False


class TestPriorityPolicy:
    """Tests for PriorityPolicy."""

    def test_defaults(self) -> None:
        policy = PriorityPolicy()
        assert policy.high_priority_patterns == []
        assert policy.low_priority_patterns == []
        assert policy.high_priority_lambda_multiplier == 1.05
        assert policy.low_priority_lambda_multiplier == 0.9

    def test_priority_multiplier_critical(self) -> None:
        policy = PriorityPolicy(critical_tags={"important"})
        meta = {"tags": ["important", "other"]}
        assert policy.get_priority_multiplier(meta) == float("inf")

    def test_priority_multiplier_high(self) -> None:
        policy = PriorityPolicy(
            high_priority_patterns=["urgent"],
            high_priority_lambda_multiplier=1.2,
        )
        meta = {"text": "This is urgent!"}
        assert policy.get_priority_multiplier(meta) == 1.2

    def test_priority_multiplier_low(self) -> None:
        policy = PriorityPolicy(
            low_priority_patterns=["test"],
            low_priority_lambda_multiplier=0.8,
        )
        meta = {"text": "This is a test message"}
        assert policy.get_priority_multiplier(meta) == 0.8

    def test_priority_multiplier_default(self) -> None:
        policy = PriorityPolicy()
        meta = {"text": "Normal message"}
        assert policy.get_priority_multiplier(meta) == 1.0


class TestMemoryPolicy:
    """Tests for MemoryPolicy."""

    def test_default_factory(self) -> None:
        policy = MemoryPolicy.default()
        assert policy.name == "default"
        assert policy.namespace == "*"
        assert policy.retention.horizon == timedelta(days=30)
        assert "kills" in policy.governance.forbidden_relations

    def test_aggressive_decay_factory(self) -> None:
        policy = MemoryPolicy.aggressive_decay()
        assert policy.name == "aggressive"
        assert policy.retention.base_lambda == 0.85
        assert policy.retention.horizon == timedelta(days=7)

    def test_long_term_factory(self) -> None:
        policy = MemoryPolicy.long_term()
        assert policy.name == "long_term"
        assert policy.retention.base_lambda == 0.99
        assert policy.retention.horizon == timedelta(days=365)

    def test_safety_critical_factory(self) -> None:
        policy = MemoryPolicy.safety_critical()
        assert policy.name == "safety_critical"
        assert policy.retention.decay_curve == DecayCurve.NONE
        assert policy.governance.audit_required is True

    def test_to_dict(self) -> None:
        policy = MemoryPolicy.default()
        d = policy.to_dict()

        assert d["apiVersion"] == "hieromem/v1"
        assert d["kind"] == "MemoryPolicy"
        assert d["metadata"]["name"] == "default"
        assert d["spec"]["retention"]["decay_curve"] == "exponential"
        assert "kills" in d["spec"]["governance"]["forbidden_relations"]
