"""Tests for rotation policies and the RotationManager."""

from __future__ import annotations

from datetime import datetime, timedelta, timezone

import pytest

from vault.models import Secret
from vault.rotation import RotationManager, RotationPolicy
from vault.store import InMemorySecretStore


class TestRotationPolicy:
    def test_defaults(self):
        p = RotationPolicy()
        assert p.max_age_days == 90
        assert p.auto_rotate is False

    def test_custom_values(self):
        p = RotationPolicy(max_age_days=30, auto_rotate=True)
        assert p.max_age_days == 30
        assert p.auto_rotate is True


class TestRotationManager:
    async def test_rotate_with_explicit_value(self, memory_store: InMemorySecretStore):
        await memory_store.set_secret("key", "original")
        manager = RotationManager(memory_store)
        rotated = await manager.rotate_secret("key", "new-value")
        assert rotated is not None
        assert rotated.version == 2
        assert await memory_store.get_latest("key") == "new-value"

    async def test_rotate_with_generator(self, memory_store: InMemorySecretStore):
        await memory_store.set_secret("api-key", "old")
        manager = RotationManager(memory_store)

        async def gen(name: str) -> str:
            return f"generated-for-{name}"

        manager.register_generator("api-key", gen)
        rotated = await manager.rotate_secret("api-key")
        assert rotated is not None
        assert await memory_store.get_latest("api-key") == "generated-for-api-key"

    async def test_rotate_without_value_or_generator_returns_none(
        self, memory_store: InMemorySecretStore
    ):
        await memory_store.set_secret("key", "val")
        manager = RotationManager(memory_store)
        assert await manager.rotate_secret("key") is None

    async def test_rotation_hook_called(self, memory_store: InMemorySecretStore):
        await memory_store.set_secret("key", "val")
        manager = RotationManager(memory_store)
        hook_calls: list[tuple[str, int]] = []

        @manager.on_rotation
        async def track(name: str, version: int) -> None:
            hook_calls.append((name, version))

        await manager.rotate_secret("key", "new")
        assert hook_calls == [("key", 2)]

    async def test_check_expiring_by_age(self, memory_store: InMemorySecretStore):
        secret = await memory_store.set_secret("old", "val")
        # Manually backdate the updated_at
        secret.updated_at = datetime.now(timezone.utc) - timedelta(days=100)
        manager = RotationManager(memory_store, RotationPolicy(max_age_days=90))
        expiring = await manager.check_expiring_secrets()
        names = [m.name for m in expiring]
        assert "old" in names

    async def test_check_expiring_by_expires_at(self, memory_store: InMemorySecretStore):
        soon = datetime.now(timezone.utc) + timedelta(days=3)
        await memory_store.set_secret("expiring", "val", expires_at=soon)
        manager = RotationManager(memory_store)
        expiring = await manager.check_expiring_secrets()
        names = [m.name for m in expiring]
        assert "expiring" in names

    async def test_fresh_secret_not_expiring(self, memory_store: InMemorySecretStore):
        await memory_store.set_secret("fresh", "val")
        manager = RotationManager(memory_store, RotationPolicy(max_age_days=90))
        expiring = await manager.check_expiring_secrets()
        assert len(expiring) == 0

    async def test_auto_rotate_all(self, memory_store: InMemorySecretStore):
        secret = await memory_store.set_secret("rotatable", "v1")
        secret.updated_at = datetime.now(timezone.utc) - timedelta(days=100)
        manager = RotationManager(memory_store, RotationPolicy(max_age_days=90))

        async def gen(name: str) -> str:
            return "auto-generated"

        manager.register_generator("rotatable", gen)
        rotated = await manager.auto_rotate_all()
        assert len(rotated) == 1
        assert rotated[0].name == "rotatable"
        assert await memory_store.get_latest("rotatable") == "auto-generated"

    async def test_auto_rotate_skips_without_generator(self, memory_store: InMemorySecretStore):
        secret = await memory_store.set_secret("no-gen", "v1")
        secret.updated_at = datetime.now(timezone.utc) - timedelta(days=100)
        manager = RotationManager(memory_store, RotationPolicy(max_age_days=90))
        rotated = await manager.auto_rotate_all()
        assert len(rotated) == 0
