"""Tests for the DecisionCache."""

import time

from pyauthz import (
    DecisionCache,
    Effect,
    PyAuthz,
    Resource,
    Role,
    Subject,
)


class TestDecisionCacheUnit:
    def test_put_and_get(self):
        cache = DecisionCache(ttl_seconds=10)
        rp = PyAuthz()
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        user = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")
        decision = rp._evaluate(user, "read", doc, {})

        key = DecisionCache.make_key("alice", ["admin"], "read", "doc-1", "documents")
        cache.put(key, decision)
        assert cache.get(key) is decision

    def test_miss_returns_none(self):
        cache = DecisionCache()
        assert cache.get("nonexistent") is None

    def test_ttl_expiration(self):
        cache = DecisionCache(ttl_seconds=0.05)
        rp = PyAuthz()
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        user = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")
        decision = rp._evaluate(user, "read", doc, {})

        key = "test-key"
        cache.put(key, decision)
        assert cache.get(key) is not None
        time.sleep(0.06)
        assert cache.get(key) is None

    def test_max_size_eviction(self):
        cache = DecisionCache(ttl_seconds=60, max_size=2)
        rp = PyAuthz()
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        user = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")
        decision = rp._evaluate(user, "read", doc, {})

        cache.put("key1", decision)
        cache.put("key2", decision)
        cache.put("key3", decision)
        # key1 should have been evicted
        assert cache.get("key1") is None
        assert cache.get("key2") is not None
        assert cache.get("key3") is not None

    def test_invalidate_clears_cache(self):
        cache = DecisionCache()
        rp = PyAuthz()
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        user = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")
        decision = rp._evaluate(user, "read", doc, {})

        cache.put("key1", decision)
        cache.put("key2", decision)
        cache.invalidate()
        assert cache.get("key1") is None
        assert cache.get("key2") is None

    def test_stats(self):
        cache = DecisionCache(ttl_seconds=60, max_size=100)
        rp = PyAuthz()
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        user = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")
        decision = rp._evaluate(user, "read", doc, {})

        cache.put("key1", decision)
        cache.get("key1")  # hit
        cache.get("key2")  # miss

        stats = cache.stats()
        assert stats["size"] == 1
        assert stats["hits"] == 1
        assert stats["misses"] == 1
        assert stats["hit_rate"] == 0.5

    def test_make_key_deterministic(self):
        key1 = DecisionCache.make_key("alice", ["admin", "editor"], "read", "doc-1", "documents")
        key2 = DecisionCache.make_key("alice", ["editor", "admin"], "read", "doc-1", "documents")
        assert key1 == key2  # Roles are sorted


class TestCacheEngineIntegration:
    def test_cache_hit_on_repeat_call(self):
        rp = PyAuthz()
        cache = DecisionCache(ttl_seconds=60)
        rp.set_cache(cache)
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        user = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")

        assert rp.is_allowed(user, "read", doc)
        assert rp.is_allowed(user, "read", doc)

        stats = cache.stats()
        assert stats["hits"] == 1
        assert stats["misses"] == 1

    def test_cache_invalidated_on_add_role(self):
        rp = PyAuthz()
        cache = DecisionCache(ttl_seconds=60)
        rp.set_cache(cache)
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        user = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")

        rp.is_allowed(user, "read", doc)
        assert cache.stats()["size"] == 1

        rp.add_role(Role(name="viewer", permissions=["documents:read"]))
        assert cache.stats()["size"] == 0  # Invalidated

    def test_cache_invalidated_on_add_policy(self):
        from pyauthz.models.policy import Policy

        rp = PyAuthz()
        cache = DecisionCache(ttl_seconds=60)
        rp.set_cache(cache)
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        user = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")

        rp.is_allowed(user, "read", doc)
        assert cache.stats()["size"] == 1

        rp.add_policy(
            Policy(name="deny-all", effect=Effect.DENY, roles=["admin"], permissions=["*:*"])
        )
        assert cache.stats()["size"] == 0

    def test_cache_invalidated_on_remove_role(self):
        rp = PyAuthz()
        cache = DecisionCache(ttl_seconds=60)
        rp.set_cache(cache)
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        user = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")

        rp.is_allowed(user, "read", doc)
        rp.remove_role("admin")
        assert cache.stats()["size"] == 0

    def test_cache_invalidated_on_replace_policies(self):
        rp = PyAuthz()
        cache = DecisionCache(ttl_seconds=60)
        rp.set_cache(cache)
        rp.add_role(Role(name="admin", permissions=["*:*"]))
        user = Subject(id="alice", roles=["admin"])
        doc = Resource(id="doc-1", type="documents")

        rp.is_allowed(user, "read", doc)
        rp.replace_policies([])
        assert cache.stats()["size"] == 0
