"""
UPDATE_SETTINGS action.

Extracts and saves setting values from natural language user messages
during the onboarding process.
"""

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

from elizaos.runtime import AgentRuntime
from elizaos.types import Action, Message

from ..types import SecretLevel, SecretContext


logger = logging.getLogger(__name__)


class UpdateSettingsAction(Action):
    """
    UPDATE_SETTINGS action.

    Saves configuration settings during the onboarding process.
    Use when onboarding with a world owner or admin.
    """

    name = "UPDATE_SETTINGS"
    description = "Saves a configuration setting during the onboarding process."
    similes = [
        "update setting", "save setting", "set configuration", "configure",
    ]
    examples = [
        [
            {"user": "user", "content": {"text": "My OpenAI key is sk-abc123def456"}},
            {"user": "assistant", "content": {"text": "Got it! I've saved your OpenAI API Key. Next, I need your Anthropic API Key."}},
        ],
        [
            {"user": "user", "content": {"text": "Here's my Twitter login: @myhandle with password secret123"}},
            {"user": "assistant", "content": {"text": "Perfect! I've updated your Twitter Username and Twitter Password. We're all set!"}},
        ],
    ]

    async def validate(self, runtime: AgentRuntime, message: Message) -> bool:
        # Only validate in DM context
        content = message.content if isinstance(message.content, dict) else {}
        channel_type = content.get("channelType", "")
        if channel_type != "DM":
            return False

        # Check for a world with unconfigured settings
        room_id = getattr(message, "room_id", None)
        if not room_id:
            return False

        room = await runtime.get_room(room_id)
        if not room:
            return False

        world_id = getattr(room, "world_id", None)
        if not world_id:
            return False

        world = await runtime.get_world(world_id)
        if not world:
            return False

        metadata = getattr(world, "metadata", None)
        if not isinstance(metadata, dict):
            return False

        settings = metadata.get("settings")
        if not settings or not isinstance(settings, dict):
            return False

        # Check for unconfigured settings
        return any(
            isinstance(s, dict) and s.get("value") is None
            for s in settings.values()
        )

    async def handler(self, runtime: AgentRuntime, message: Message, state: Dict) -> Dict:
        room_id = getattr(message, "room_id", None)
        room = await runtime.get_room(room_id) if room_id else None
        if not room:
            return {"success": False, "message": "Unable to find room configuration."}

        world_id = getattr(room, "world_id", None)
        if not world_id:
            return {"success": False, "message": "No world for this room."}

        world = await runtime.get_world(world_id)
        if not world:
            return {"success": False, "message": "World not found."}

        metadata = getattr(world, "metadata", None)
        if not isinstance(metadata, dict):
            return {"success": False, "message": "No settings configured."}

        settings = metadata.get("settings", {})

        # Get secrets service
        from ..service import SecretsService
        secrets_service = runtime.get_service(SecretsService.service_type)

        text = ""
        if isinstance(state, dict):
            text = state.get("text", "")
        if not text and isinstance(message.content, dict):
            text = message.content.get("text", "")

        # Extract settings from message
        extracted = await self._extract_setting_values(runtime, text, settings)

        if not extracted:
            next_setting = self._get_next_required(settings)
            if next_setting:
                key, setting = next_setting
                name = setting.get("name", key)
                desc = setting.get("usageDescription") or setting.get("description", "")
                return {
                    "success": False,
                    "message": f"I couldn't understand that. I need your {name}. {desc}",
                }
            return {"success": False, "message": "I couldn't extract any settings from your message."}

        # Process updates
        messages = []
        updated_any = False

        for update in extracted:
            key = update["key"]
            value = str(update["value"])
            setting = settings.get(key)
            if not setting:
                continue

            # Check dependencies
            depends_on = setting.get("dependsOn", [])
            deps_met = all(
                settings.get(dep, {}).get("value") is not None
                for dep in depends_on
            )
            if not deps_met:
                name = setting.get("name", key)
                messages.append(f"Cannot update {name} - dependencies not met")
                continue

            # Validate if method specified
            validation_method = setting.get("validationMethod")
            if validation_method:
                from ..validation import validate_secret
                result = await validate_secret(key, value, validation_method)
                if not result.is_valid:
                    name = setting.get("name", key)
                    messages.append(f"Validation failed for {name}: {result.error}")
                    continue

            # Update settings
            setting["value"] = value

            # Store in secrets service
            if secrets_service:
                context = SecretContext(
                    level=SecretLevel.WORLD,
                    agent_id=runtime.agent_id,
                    world_id=world_id,
                )
                await secrets_service.set(key, value, context, {
                    "description": setting.get("description", ""),
                    "type": setting.get("type", "api_key"),
                    "encrypted": setting.get("secret", True),
                })

            name = setting.get("name", key)
            messages.append(f"Updated {name} successfully")
            updated_any = True

        # Persist to world
        if updated_any:
            metadata["settings"] = settings
            world.metadata = metadata
            await runtime.update_world(world)

        if updated_any:
            remaining = self._count_unconfigured_required(settings)
            if remaining == 0:
                return {
                    "success": True,
                    "message": "\n".join(messages) + "\n\nAll required settings have been configured! You're all set.",
                    "onboarding_complete": True,
                }

            next_setting = self._get_next_required(settings)
            next_prompt = ""
            if next_setting:
                key, setting = next_setting
                name = setting.get("name", key)
                desc = setting.get("usageDescription") or setting.get("description", "")
                next_prompt = f"\n\nNext, I need your {name}. {desc}"

            return {
                "success": True,
                "message": "\n".join(messages) + next_prompt,
                "remaining_required": remaining,
            }

        return {"success": False, "message": "\n".join(messages) if messages else "No settings updated."}

    async def _extract_setting_values(
        self,
        runtime: AgentRuntime,
        text: str,
        settings: Dict[str, Any],
    ) -> List[Dict[str, Any]]:
        """Extract setting values from user text."""
        if not text:
            return []

        unconfigured = {
            k: s for k, s in settings.items()
            if isinstance(s, dict) and s.get("value") is None
        }

        if not unconfigured:
            return []

        # Build context for LLM extraction
        settings_context = "\n".join(
            f"{key}: {s.get('description', '')} {'Required.' if s.get('required') else 'Optional.'}"
            for key, s in unconfigured.items()
        )

        prompt = (
            f"I need to extract settings values from the user's message.\n\n"
            f"Available settings:\n{settings_context}\n\n"
            f"User message: {text}\n\n"
            f"For each setting mentioned in the user's message, extract the value.\n"
            f"Only return settings that are clearly mentioned.\n"
            f'Return JSON array: [{{"key": "KEY_NAME", "value": "extracted_value"}}]'
        )

        import json
        import re
        try:
            response = await runtime.generate_text(prompt, {"temperature": 0.1, "max_tokens": 500})
            # Find JSON array in response
            array_match = re.search(r"\[.*\]", response, re.DOTALL)
            if array_match:
                parsed = json.loads(array_match.group())
                if isinstance(parsed, list):
                    # Validate keys exist
                    return [
                        item for item in parsed
                        if isinstance(item, dict)
                        and item.get("key") in settings
                        and item.get("value")
                    ]
        except Exception as e:
            logger.debug(f"[UpdateSettings] LLM extraction failed: {e}")

        # Fallback: try direct key=value pattern matching
        results = []
        for key in unconfigured:
            import re as re_mod
            pattern = rf"(?:^|\s){re_mod.escape(key)}\s*[:=]\s*(\S+)"
            match = re_mod.search(pattern, text, re_mod.IGNORECASE)
            if match:
                results.append({"key": key, "value": match.group(1)})

        return results

    def _get_next_required(self, settings: Dict[str, Any]) -> Optional[tuple]:
        """Get next unconfigured required setting."""
        for key, setting in settings.items():
            if not isinstance(setting, dict):
                continue
            if not setting.get("required") or setting.get("value") is not None:
                continue
            depends_on = setting.get("dependsOn", [])
            deps_met = all(
                settings.get(dep, {}).get("value") is not None
                for dep in depends_on
            )
            if deps_met:
                return (key, setting)
        return None

    def _count_unconfigured_required(self, settings: Dict[str, Any]) -> int:
        """Count unconfigured required settings."""
        return sum(
            1 for s in settings.values()
            if isinstance(s, dict) and s.get("required") and s.get("value") is None
        )
