"""
Tests for persistent storage backends:
- CharacterSettingsStorage
- WorldMetadataStorage
- ComponentSecretStorage
"""

import pytest
import asyncio
import time
from unittest.mock import AsyncMock, MagicMock, patch

from elizaos_plugin_secrets_manager.types import (
    SecretContext, SecretLevel, SecretConfig, StorageBackend,
)
from elizaos_plugin_secrets_manager.crypto import KeyManager
from elizaos_plugin_secrets_manager.storage.character_settings import CharacterSettingsStorage
from elizaos_plugin_secrets_manager.storage.world_metadata import WorldMetadataStorage
from elizaos_plugin_secrets_manager.storage.component_storage import ComponentSecretStorage
from elizaos_plugin_secrets_manager.storage.memory_store import MemorySecretStorage
from elizaos_plugin_secrets_manager.storage.interface import CompositeSecretStorage


# ============================================================================
# Fixtures
# ============================================================================

def make_key_manager():
    km = KeyManager()
    km.initialize_from_agent_id("test-agent", "test-salt")
    return km


def make_runtime(agent_id="test-agent"):
    runtime = MagicMock()
    runtime.agent_id = agent_id
    runtime.character = MagicMock()
    runtime.character.settings = {"secrets": {}}
    runtime.character.name = "TestBot"
    runtime.get_setting = MagicMock(return_value=None)
    return runtime


def global_context(agent_id="test-agent"):
    return SecretContext(level=SecretLevel.GLOBAL, agent_id=agent_id)


def world_context(agent_id="test-agent", world_id="world-1"):
    return SecretContext(level=SecretLevel.WORLD, agent_id=agent_id, world_id=world_id)


def user_context(agent_id="test-agent", user_id="user-1"):
    return SecretContext(level=SecretLevel.USER, agent_id=agent_id, user_id=user_id, requester_id=user_id)


# ============================================================================
# CharacterSettingsStorage Tests
# ============================================================================

class TestCharacterSettingsStorage:
    @pytest.fixture
    def storage(self):
        runtime = make_runtime()
        km = make_key_manager()
        return CharacterSettingsStorage(runtime, km)

    @pytest.mark.asyncio
    async def test_storage_type(self, storage):
        assert storage.storage_type == StorageBackend.CHARACTER

    @pytest.mark.asyncio
    async def test_initialize(self, storage):
        await storage.initialize()
        # Should not raise, second call should be no-op
        await storage.initialize()

    @pytest.mark.asyncio
    async def test_set_and_get(self, storage):
        ctx = global_context()
        await storage.initialize()

        result = await storage.set("API_KEY", "sk-test123", ctx)
        assert result is True

        value = await storage.get("API_KEY", ctx)
        assert value == "sk-test123"

    @pytest.mark.asyncio
    async def test_exists(self, storage):
        ctx = global_context()
        await storage.initialize()

        assert await storage.exists("API_KEY", ctx) is False
        await storage.set("API_KEY", "test", ctx)
        assert await storage.exists("API_KEY", ctx) is True

    @pytest.mark.asyncio
    async def test_delete(self, storage):
        ctx = global_context()
        await storage.initialize()

        await storage.set("API_KEY", "test", ctx)
        assert await storage.delete("API_KEY", ctx) is True
        assert await storage.exists("API_KEY", ctx) is False
        assert await storage.delete("API_KEY", ctx) is False

    @pytest.mark.asyncio
    async def test_list(self, storage):
        ctx = global_context()
        await storage.initialize()

        await storage.set("KEY1", "val1", ctx)
        await storage.set("KEY2", "val2", ctx)

        metadata = await storage.list(ctx)
        assert "KEY1" in metadata
        assert "KEY2" in metadata

    @pytest.mark.asyncio
    async def test_get_config(self, storage):
        ctx = global_context()
        await storage.initialize()

        await storage.set("API_KEY", "test", ctx, {"description": "Test key"})
        config = await storage.get_config("API_KEY", ctx)
        assert config is not None

    @pytest.mark.asyncio
    async def test_update_config(self, storage):
        ctx = global_context()
        await storage.initialize()

        await storage.set("API_KEY", "test", ctx)
        result = await storage.update_config("API_KEY", ctx, {"description": "Updated"})
        assert result is True

    @pytest.mark.asyncio
    async def test_nonexistent_returns_none(self, storage):
        ctx = global_context()
        await storage.initialize()

        assert await storage.get("MISSING", ctx) is None
        assert await storage.get_config("MISSING", ctx) is None


# ============================================================================
# WorldMetadataStorage Tests
# ============================================================================

class TestWorldMetadataStorage:
    @pytest.fixture
    def storage(self):
        runtime = make_runtime()
        km = make_key_manager()

        # Mock world operations
        world = MagicMock()
        world.id = "world-1"
        world.metadata = {"roles": {"test-agent": "OWNER"}, "secrets": {}}
        runtime.get_world = AsyncMock(return_value=world)
        runtime.update_world = AsyncMock()

        return WorldMetadataStorage(runtime, km), world

    @pytest.mark.asyncio
    async def test_storage_type(self, storage):
        ws, _ = storage
        assert ws.storage_type == StorageBackend.WORLD

    @pytest.mark.asyncio
    async def test_set_and_get(self, storage):
        ws, world = storage
        ctx = world_context()

        result = await ws.set("DISCORD_TOKEN", "token123", ctx)
        assert result is True

        value = await ws.get("DISCORD_TOKEN", ctx)
        assert value == "token123"

    @pytest.mark.asyncio
    async def test_no_world_id_returns_none(self, storage):
        ws, _ = storage
        ctx = SecretContext(level=SecretLevel.WORLD, agent_id="test-agent")
        assert await ws.get("KEY", ctx) is None
        assert await ws.exists("KEY", ctx) is False

    @pytest.mark.asyncio
    async def test_delete(self, storage):
        ws, _ = storage
        ctx = world_context()

        await ws.set("KEY", "value", ctx)
        assert await ws.delete("KEY", ctx) is True
        assert await ws.exists("KEY", ctx) is False

    @pytest.mark.asyncio
    async def test_list(self, storage):
        ws, _ = storage
        ctx = world_context()

        await ws.set("KEY1", "val1", ctx)
        await ws.set("KEY2", "val2", ctx)

        metadata = await ws.list(ctx)
        assert "KEY1" in metadata
        assert "KEY2" in metadata

    @pytest.mark.asyncio
    async def test_cache_operations(self, storage):
        ws, _ = storage
        ws.clear_cache()
        ws.invalidate_world("world-1")


# ============================================================================
# ComponentSecretStorage Tests
# ============================================================================

class TestComponentSecretStorage:
    @pytest.fixture
    def storage(self):
        runtime = make_runtime()
        km = make_key_manager()

        # Mock component operations
        components = []
        runtime.get_components = AsyncMock(return_value=components)
        runtime.create_component = AsyncMock()
        runtime.update_component = AsyncMock()
        runtime.delete_component = AsyncMock()

        return ComponentSecretStorage(runtime, km), components, runtime

    @pytest.mark.asyncio
    async def test_storage_type(self, storage):
        cs, _, _ = storage
        assert cs.storage_type == StorageBackend.COMPONENT

    @pytest.mark.asyncio
    async def test_no_user_id_returns_none(self, storage):
        cs, _, _ = storage
        ctx = SecretContext(level=SecretLevel.USER, agent_id="test-agent")
        assert await cs.get("KEY", ctx) is None
        assert await cs.exists("KEY", ctx) is False

    @pytest.mark.asyncio
    async def test_set_creates_component(self, storage):
        cs, components, runtime = storage
        ctx = user_context()

        result = await cs.set("USER_SECRET", "secret-value", ctx)
        assert result is True
        runtime.create_component.assert_called_once()

    @pytest.mark.asyncio
    async def test_set_updates_existing(self, storage):
        cs, components, runtime = storage
        ctx = user_context()

        # Add existing component
        existing = MagicMock()
        existing.type = "secret"
        existing.data = {"key": "USER_SECRET", "value": "old", "config": {}}
        existing.id = "comp-1"
        components.append(existing)

        result = await cs.set("USER_SECRET", "new-value", ctx)
        assert result is True
        runtime.update_component.assert_called_once()

    @pytest.mark.asyncio
    async def test_delete(self, storage):
        cs, components, runtime = storage
        ctx = user_context()

        component = MagicMock()
        component.type = "secret"
        component.data = {"key": "USER_SECRET", "value": "val", "config": {}}
        component.id = "comp-1"
        components.append(component)

        result = await cs.delete("USER_SECRET", ctx)
        assert result is True
        runtime.delete_component.assert_called_once_with("comp-1")

    @pytest.mark.asyncio
    async def test_list(self, storage):
        cs, components, _ = storage
        ctx = user_context()

        comp1 = MagicMock()
        comp1.type = "secret"
        comp1.data = {
            "key": "KEY1",
            "value": "val1",
            "config": {"type": "secret", "required": False, "description": "k1",
                       "canGenerate": False, "status": "valid", "plugin": "test",
                       "level": "user"},
        }
        comp2 = MagicMock()
        comp2.type = "other"
        comp2.data = {}

        components.extend([comp1, comp2])

        metadata = await cs.list(ctx)
        assert "KEY1" in metadata
        assert len(metadata) == 1

    @pytest.mark.asyncio
    async def test_list_keys(self, storage):
        cs, components, _ = storage

        comp = MagicMock()
        comp.type = "secret"
        comp.data = {"key": "MY_KEY"}
        components.append(comp)

        keys = await cs.list_keys("user-1")
        assert "MY_KEY" in keys


# ============================================================================
# CompositeSecretStorage Tests
# ============================================================================

class TestCompositeStorage:
    @pytest.mark.asyncio
    async def test_delegates_to_correct_backend(self):
        global_store = MemorySecretStorage()
        world_store = MemorySecretStorage()
        user_store = MemorySecretStorage()

        composite = CompositeSecretStorage(global_store, world_store, user_store)
        await composite.initialize()

        g_ctx = global_context()
        w_ctx = world_context()
        u_ctx = user_context()

        await composite.set("KEY", "global-val", g_ctx)
        await composite.set("KEY", "world-val", w_ctx)
        await composite.set("KEY", "user-val", u_ctx)

        assert await composite.get("KEY", g_ctx) == "global-val"
        assert await composite.get("KEY", w_ctx) == "world-val"
        assert await composite.get("KEY", u_ctx) == "user-val"
