"""Tests for the Policy Engine module."""

from __future__ import annotations

import json

import pytest

from mcpaudit.policy_engine import (
    EvaluationResult,
    PolicyAction,
    PolicyCondition,
    PolicyEngine,
    PolicyResult,
    PolicyRule,
)


# ===========================================================================
# PolicyCondition
# ===========================================================================

class TestPolicyCondition:
    def test_equals_true(self):
        c = PolicyCondition("tier", "equals", "ReadOnly")
        assert c.evaluate({"tier": "ReadOnly"})

    def test_equals_false(self):
        c = PolicyCondition("tier", "equals", "ReadOnly")
        assert not c.evaluate({"tier": "FullAccess"})

    def test_not_equals(self):
        c = PolicyCondition("tier", "not_equals", "FullAccess")
        assert c.evaluate({"tier": "ReadOnly"})

    def test_contains_list(self):
        c = PolicyCondition("tools", "contains", "Bash")
        assert c.evaluate({"tools": ["Read", "Bash", "Write"]})

    def test_contains_list_false(self):
        c = PolicyCondition("tools", "contains", "Bash")
        assert not c.evaluate({"tools": ["Read", "Write"]})

    def test_not_contains(self):
        c = PolicyCondition("tools", "not_contains", "Bash")
        assert c.evaluate({"tools": ["Read", "Write"]})

    def test_in(self):
        c = PolicyCondition("tier", "in", ["ReadOnly", "WorkspaceWrite"])
        assert c.evaluate({"tier": "ReadOnly"})

    def test_not_in(self):
        c = PolicyCondition("tier", "not_in", ["FullAccess"])
        assert c.evaluate({"tier": "ReadOnly"})

    def test_greater_than(self):
        c = PolicyCondition("tool_count", "greater_than", 5)
        assert c.evaluate({"tool_count": 10})
        assert not c.evaluate({"tool_count": 3})

    def test_less_than(self):
        c = PolicyCondition("count", "less_than", 5)
        assert c.evaluate({"count": 3})
        assert not c.evaluate({"count": 10})

    def test_exists(self):
        c = PolicyCondition("field", "exists", True)
        assert c.evaluate({"field": "value"})
        assert not c.evaluate({})

    def test_missing_field(self):
        c = PolicyCondition("missing", "equals", "x")
        assert not c.evaluate({})

    def test_to_dict(self):
        c = PolicyCondition("tier", "equals", "ReadOnly", "check tier")
        d = c.to_dict()
        assert d["field"] == "tier"
        assert d["operator"] == "equals"

    def test_from_dict(self):
        data = {"field": "tools", "operator": "contains", "value": "Bash"}
        c = PolicyCondition.from_dict(data)
        assert c.field == "tools"

    def test_roundtrip(self):
        c = PolicyCondition("x", "equals", 42, "desc")
        c2 = PolicyCondition.from_dict(c.to_dict())
        assert c == c2


# ===========================================================================
# PolicyRule
# ===========================================================================

class TestPolicyRule:
    def test_evaluate_pass(self):
        rule = PolicyRule(
            rule_id="R1",
            name="test",
            conditions=(PolicyCondition("tier", "equals", "FullAccess"),),
            action=PolicyAction.DENY,
        )
        # Condition doesn't match → PASS
        result = rule.evaluate({"tier": "ReadOnly"})
        assert result == PolicyResult.PASS

    def test_evaluate_fail(self):
        rule = PolicyRule(
            rule_id="R1",
            name="test",
            conditions=(PolicyCondition("tier", "equals", "FullAccess"),),
            action=PolicyAction.DENY,
        )
        result = rule.evaluate({"tier": "FullAccess"})
        assert result == PolicyResult.FAIL

    def test_evaluate_warn(self):
        rule = PolicyRule(
            rule_id="R1",
            name="test",
            conditions=(PolicyCondition("tools", "contains", "WebFetch"),),
            action=PolicyAction.WARN,
        )
        result = rule.evaluate({"tools": ["WebFetch"]})
        assert result == PolicyResult.WARNING

    def test_evaluate_no_conditions(self):
        rule = PolicyRule(
            rule_id="R1",
            name="test",
            conditions=(),
            action=PolicyAction.DENY,
        )
        assert rule.evaluate({}) == PolicyResult.NOT_APPLICABLE

    def test_match_all_and(self):
        rule = PolicyRule(
            rule_id="R1",
            name="test",
            conditions=(
                PolicyCondition("a", "equals", 1),
                PolicyCondition("b", "equals", 2),
            ),
            action=PolicyAction.DENY,
            match_all=True,
        )
        assert rule.evaluate({"a": 1, "b": 2}) == PolicyResult.FAIL
        assert rule.evaluate({"a": 1, "b": 3}) == PolicyResult.PASS

    def test_match_any_or(self):
        rule = PolicyRule(
            rule_id="R1",
            name="test",
            conditions=(
                PolicyCondition("a", "equals", 1),
                PolicyCondition("b", "equals", 2),
            ),
            action=PolicyAction.DENY,
            match_all=False,
        )
        assert rule.evaluate({"a": 1, "b": 3}) == PolicyResult.FAIL

    def test_to_dict(self):
        rule = PolicyRule(
            rule_id="R1",
            name="test",
            conditions=(PolicyCondition("x", "equals", 1),),
            action=PolicyAction.DENY,
            severity="HIGH",
        )
        d = rule.to_dict()
        assert d["rule_id"] == "R1"
        assert d["action"] == "DENY"

    def test_from_dict(self):
        data = {
            "rule_id": "R1",
            "name": "test",
            "conditions": [{"field": "x", "operator": "equals", "value": 1}],
            "action": "WARN",
        }
        rule = PolicyRule.from_dict(data)
        assert rule.action == PolicyAction.WARN

    def test_roundtrip(self):
        rule = PolicyRule(
            rule_id="R1",
            name="test",
            conditions=(PolicyCondition("x", "equals", 1),),
            action=PolicyAction.DENY,
            severity="CRITICAL",
            remediation="fix it",
        )
        rule2 = PolicyRule.from_dict(rule.to_dict())
        assert rule2.rule_id == rule.rule_id
        assert rule2.remediation == "fix it"


# ===========================================================================
# PolicyEngine
# ===========================================================================

class TestPolicyEngine:
    def test_has_builtins(self):
        engine = PolicyEngine()
        assert engine.rule_count >= 5

    def test_add_remove_rule(self):
        engine = PolicyEngine()
        initial = engine.rule_count
        engine.add_rule(PolicyRule("CUSTOM-1", "custom", (), PolicyAction.DENY))
        assert engine.rule_count == initial + 1
        assert engine.remove_rule("CUSTOM-1")
        assert engine.rule_count == initial

    def test_get_rule(self):
        engine = PolicyEngine()
        assert engine.get_rule("POL-001") is not None
        assert engine.get_rule("NONEXISTENT") is None

    def test_evaluate_clean_config(self):
        engine = PolicyEngine()
        results = engine.evaluate({
            "sandbox_tier": "ReadOnly",
            "tools": ["Read", "Grep"],
            "tool_count": 2,
            "constraint_count": 3,
            "environment": "development",
            "has_audit": True,
        })
        failures = engine.failures(results)
        assert len(failures) == 0

    def test_evaluate_fails_full_access_prod(self):
        engine = PolicyEngine()
        results = engine.evaluate({
            "sandbox_tier": "FullAccess",
            "environment": "production",
            "tools": [],
            "tool_count": 0,
            "constraint_count": 0,
            "has_audit": True,
        })
        failures = engine.failures(results)
        pol001 = [f for f in failures if f.rule_id == "POL-001"]
        assert len(pol001) == 1

    def test_evaluate_warns_webfetch(self):
        engine = PolicyEngine()
        results = engine.evaluate({
            "tools": ["WebFetch"],
            "tool_count": 1,
            "sandbox_tier": "ReadOnly",
            "constraint_count": 1,
            "has_audit": True,
        })
        warns = engine.warnings(results)
        assert len(warns) >= 1

    def test_compliance_score_perfect(self):
        engine = PolicyEngine()
        results = engine.evaluate({
            "sandbox_tier": "ReadOnly",
            "tools": ["Read"],
            "tool_count": 1,
            "constraint_count": 5,
            "has_audit": True,
        })
        score = engine.compliance_score(results)
        assert score == 1.0

    def test_compliance_score_partial(self):
        engine = PolicyEngine()
        results = engine.evaluate({
            "sandbox_tier": "FullAccess",
            "environment": "production",
            "tools": ["Bash", "MCP", "WebFetch"],
            "tool_count": 10,
            "constraint_count": 0,
            "has_audit": False,
        })
        score = engine.compliance_score(results)
        assert score < 1.0

    def test_evaluate_batch(self):
        engine = PolicyEngine()
        contexts = [
            {"name": "safe", "sandbox_tier": "ReadOnly", "tools": [], "tool_count": 0,
             "constraint_count": 1, "has_audit": True},
            {"name": "risky", "sandbox_tier": "FullAccess", "environment": "production",
             "tools": ["Bash"], "tool_count": 1, "constraint_count": 0, "has_audit": False},
        ]
        batch_results = engine.evaluate_batch(contexts)
        assert "safe" in batch_results
        assert "risky" in batch_results

    def test_audit_log(self):
        engine = PolicyEngine()
        engine.evaluate({"tools": [], "tool_count": 0, "constraint_count": 0, "has_audit": True})
        assert len(engine.audit_log) > 0
        engine.clear_audit_log()
        assert len(engine.audit_log) == 0

    # -- Persistence --

    def test_to_json(self):
        engine = PolicyEngine()
        j = engine.to_json()
        data = json.loads(j)
        assert data["version"] == "1.0"
        assert len(data["rules"]) >= 5

    def test_from_json(self):
        engine = PolicyEngine()
        j = engine.to_json()
        engine2 = PolicyEngine.from_json(j)
        assert engine2.rule_count == engine.rule_count

    def test_roundtrip(self):
        engine = PolicyEngine()
        engine.add_rule(PolicyRule("X-1", "custom", (), PolicyAction.WARN))
        j = engine.to_json()
        engine2 = PolicyEngine.from_json(j)
        assert engine2.get_rule("X-1") is not None

    # -- Report --

    def test_markdown_report(self):
        engine = PolicyEngine()
        results = engine.evaluate({
            "sandbox_tier": "FullAccess",
            "environment": "production",
            "tools": ["Bash", "MCP"],
            "tool_count": 2,
            "constraint_count": 0,
            "has_audit": False,
        })
        report = engine.markdown_report(results)
        assert "# Policy Evaluation Report" in report
        assert "Failures" in report
