"""
World Metadata Storage

Stores world-level secrets in the world's metadata.secrets object.
Used for server/channel-specific configuration like Discord tokens.
Access control is enforced based on world roles (OWNER/ADMIN can write).
"""

import logging
import time
from typing import Optional, Dict, Any

from ..types import (
    SecretConfig,
    SecretContext,
    SecretMetadata,
    StorageBackend,
    PermissionDeniedError,
    StorageError,
)
from ..crypto import KeyManager, is_encrypted_secret
from .interface import SecretStorageInterface


logger = logging.getLogger(__name__)


class WorldMetadataStorage(SecretStorageInterface):
    """
    World metadata-based storage for world-level secrets.

    Secrets are stored in world.metadata.secrets with access control
    based on world roles (OWNER/ADMIN can write, all members can read).
    """

    def __init__(self, runtime: Any, key_manager: KeyManager):
        self._runtime = runtime
        self._key_manager = key_manager
        self._world_cache: Dict[str, Any] = {}

    @property
    def storage_type(self) -> StorageBackend:
        return StorageBackend.WORLD

    async def initialize(self) -> None:
        logger.debug("[WorldMetadataStorage] Initialized")

    async def _get_world(self, world_id: str) -> Optional[Any]:
        """Get a world from cache or database."""
        cached = self._world_cache.get(world_id)
        if cached:
            return cached
        world = await self._runtime.get_world(world_id)
        if world:
            self._world_cache[world_id] = world
        return world

    def _get_world_secrets(self, world: Any) -> Dict[str, Any]:
        """Get secrets dict from world metadata."""
        metadata = getattr(world, "metadata", None)
        if not metadata or not isinstance(metadata, dict):
            return {}
        return metadata.get("secrets", {})

    async def _check_write_permission(self, world_id: str, user_id: str) -> bool:
        """Check if a user has write permission in a world."""
        world = await self._get_world(world_id)
        if not world:
            return False
        # Agent always has permission
        if user_id == self._runtime.agent_id:
            return True
        metadata = getattr(world, "metadata", None)
        if not metadata or not isinstance(metadata, dict):
            return False
        roles = metadata.get("roles", {})
        user_role = roles.get(user_id, "")
        return user_role in ("OWNER", "ADMIN", "owner", "admin")

    async def exists(self, key: str, context: SecretContext) -> bool:
        if not context.world_id:
            return False
        world = await self._get_world(context.world_id)
        if not world:
            return False
        secrets = self._get_world_secrets(world)
        return key in secrets

    async def get(self, key: str, context: SecretContext) -> Optional[str]:
        if not context.world_id:
            logger.warn("[WorldMetadataStorage] Cannot get secret without worldId")
            return None

        world = await self._get_world(context.world_id)
        if not world:
            return None
        secrets = self._get_world_secrets(world)
        stored = secrets.get(key)

        if stored is None:
            return None

        if isinstance(stored, str):
            return stored

        if isinstance(stored, dict):
            value = stored.get("value")
            config_data = stored.get("config", {})

            # Check expiration
            expires_at = config_data.get("expiresAt")
            if expires_at and expires_at < int(time.time() * 1000):
                await self.delete(key, context)
                return None

            if is_encrypted_secret(value):
                return self._key_manager.decrypt(value)

            if isinstance(value, str):
                return value

        return None

    async def set(
        self,
        key: str,
        value: str,
        context: SecretContext,
        config: Optional[Dict] = None,
    ) -> bool:
        if not context.world_id:
            raise StorageError("Cannot set world secret without worldId")

        # Check write permission
        if context.requester_id:
            has_perm = await self._check_write_permission(context.world_id, context.requester_id)
            if not has_perm:
                from ..types import SecretPermissionType
                raise PermissionDeniedError(key, SecretPermissionType.WRITE, context)

        world = await self._get_world(context.world_id)
        if not world:
            raise StorageError(f"World not found: {context.world_id}")

        # Ensure metadata structure
        metadata = getattr(world, "metadata", None)
        if not metadata or not isinstance(metadata, dict):
            world.metadata = {}
            metadata = world.metadata
        if "secrets" not in metadata:
            metadata["secrets"] = {}

        secrets = metadata["secrets"]
        existing = secrets.get(key)
        existing_config = None
        if isinstance(existing, dict) and "config" in existing:
            existing_config = existing["config"]

        merged = {}
        if existing_config:
            merged.update(existing_config)
        if config:
            merged.update(config)
        merged["worldId"] = context.world_id

        full_config = self._create_default_config(key, context, merged)

        should_encrypt = full_config.encrypted is not False
        if should_encrypt:
            stored_value = self._key_manager.encrypt(value).to_dict()
        else:
            stored_value = value

        secrets[key] = {
            "value": stored_value,
            "config": full_config.to_dict(),
        }

        await self._runtime.update_world(world)
        self._world_cache[context.world_id] = world

        logger.debug(f"[WorldMetadataStorage] Set secret: {key} for world: {context.world_id}")
        return True

    async def delete(self, key: str, context: SecretContext) -> bool:
        if not context.world_id:
            return False

        if context.requester_id:
            has_perm = await self._check_write_permission(context.world_id, context.requester_id)
            if not has_perm:
                from ..types import SecretPermissionType
                raise PermissionDeniedError(key, SecretPermissionType.DELETE, context)

        world = await self._get_world(context.world_id)
        if not world:
            return False

        metadata = getattr(world, "metadata", None)
        if not metadata or not isinstance(metadata, dict):
            return False
        secrets = metadata.get("secrets", {})
        if key not in secrets:
            return False

        del secrets[key]
        await self._runtime.update_world(world)
        self._world_cache[context.world_id] = world

        logger.debug(f"[WorldMetadataStorage] Deleted secret: {key} from world: {context.world_id}")
        return True

    async def list(self, context: SecretContext) -> SecretMetadata:
        if not context.world_id:
            return {}

        world = await self._get_world(context.world_id)
        if not world:
            return {}

        secrets = self._get_world_secrets(world)
        metadata: SecretMetadata = {}
        now = int(time.time() * 1000)

        for key, stored in secrets.items():
            if isinstance(stored, dict) and "config" in stored:
                config_data = stored["config"]
                expires_at = config_data.get("expiresAt")
                if expires_at and expires_at < now:
                    continue
                metadata[key] = SecretConfig.from_dict(config_data)
            elif isinstance(stored, str):
                metadata[key] = self._create_default_config(key, context)

        return metadata

    async def get_config(self, key: str, context: SecretContext) -> Optional[SecretConfig]:
        if not context.world_id:
            return None

        world = await self._get_world(context.world_id)
        if not world:
            return None

        secrets = self._get_world_secrets(world)
        stored = secrets.get(key)
        if not stored:
            return None

        if isinstance(stored, dict) and "config" in stored:
            return SecretConfig.from_dict(stored["config"])

        return self._create_default_config(key, context)

    async def update_config(
        self,
        key: str,
        context: SecretContext,
        config: Dict,
    ) -> bool:
        if not context.world_id:
            return False

        if context.requester_id:
            has_perm = await self._check_write_permission(context.world_id, context.requester_id)
            if not has_perm:
                from ..types import SecretPermissionType
                raise PermissionDeniedError(key, SecretPermissionType.WRITE, context)

        world = await self._get_world(context.world_id)
        if not world:
            return False

        metadata = getattr(world, "metadata", None)
        if not metadata or not isinstance(metadata, dict):
            return False
        secrets = metadata.get("secrets", {})
        stored = secrets.get(key)
        if not stored:
            return False

        if isinstance(stored, dict) and "config" in stored:
            stored["config"].update(config)
        elif isinstance(stored, str):
            default_config = self._create_default_config(key, context)
            merged = default_config.to_dict()
            merged.update(config)
            secrets[key] = {"value": stored, "config": merged}

        await self._runtime.update_world(world)
        self._world_cache[context.world_id] = world
        return True

    def clear_cache(self) -> None:
        """Clear the world cache."""
        self._world_cache.clear()

    def invalidate_world(self, world_id: str) -> None:
        """Invalidate a specific world in the cache."""
        self._world_cache.pop(world_id, None)
