"""
Onboarding Service for secrets collection.

Manages conversational secret onboarding flows across platforms.
Detects missing secrets, creates step-by-step guided setup flows,
and processes user responses during onboarding.
"""

import logging
import time
from dataclasses import dataclass, field
from typing import Optional, Dict, List, Any, Tuple, Callable

from .types import SecretType, SecretContext, SecretLevel


logger = logging.getLogger(__name__)


# ============================================================================
# Onboarding Configuration Types
# ============================================================================

@dataclass
class OnboardingSetting:
    """Setting definition for onboarding."""
    name: str
    description: str
    secret: bool
    public: bool
    required: bool
    depends_on: List[str] = field(default_factory=list)
    usage_description: Optional[str] = None
    validation_method: Optional[str] = None
    type: Optional[str] = None
    env_var: Optional[str] = None
    default_value: Optional[str] = None
    value: Optional[str] = None


@dataclass
class OnboardingConfig:
    """Onboarding configuration for an agent or plugin."""
    settings: Dict[str, OnboardingSetting]
    messages: Optional[Dict[str, Any]] = None
    mode: str = "conversational"


@dataclass
class OnboardingFlowStep:
    """A single step in an onboarding flow."""
    key: str
    setting: OnboardingSetting
    prompt: str
    step_number: int
    total_steps: int
    is_required: bool


@dataclass
class OnboardingStepResult:
    """Result of processing an onboarding step."""
    success: bool
    next_step: Optional[OnboardingFlowStep] = None
    complete: bool = False
    message: Optional[str] = None
    updated_key: Optional[str] = None
    error: Optional[str] = None


# ============================================================================
# Default Messages
# ============================================================================

DEFAULT_ONBOARDING_MESSAGES = {
    "welcome": [
        "Hi! I need to collect some information to get set up. Is now a good time?",
        "Hey there! I need to configure a few things. Do you have a moment?",
        "Hello! Could we take a few minutes to get everything set up?",
    ],
    "ask_setting": "I need your {setting_name}. {usage_description}",
    "setting_updated": "Got it! I've saved your {setting_name}.",
    "all_complete": "Great! All required settings have been configured. You're all set!",
    "error": "I had trouble understanding that. Could you try again?",
}

# ============================================================================
# Common API Key Settings
# ============================================================================

COMMON_API_KEY_SETTINGS: Dict[str, Dict[str, Any]] = {
    "OPENAI_API_KEY": {
        "name": "OpenAI API Key",
        "description": "API key for OpenAI services (GPT models)",
        "usage_description": 'Your OpenAI API key starts with "sk-"',
        "secret": True, "public": False, "required": False,
        "depends_on": [], "validation_method": "openai",
        "type": "api_key", "env_var": "OPENAI_API_KEY",
    },
    "ANTHROPIC_API_KEY": {
        "name": "Anthropic API Key",
        "description": "API key for Anthropic services (Claude models)",
        "usage_description": 'Your Anthropic API key starts with "sk-ant-"',
        "secret": True, "public": False, "required": False,
        "depends_on": [], "validation_method": "anthropic",
        "type": "api_key", "env_var": "ANTHROPIC_API_KEY",
    },
    "GROQ_API_KEY": {
        "name": "Groq API Key",
        "description": "API key for Groq inference services",
        "usage_description": 'Your Groq API key starts with "gsk_"',
        "secret": True, "public": False, "required": False,
        "depends_on": [], "validation_method": "groq",
        "type": "api_key", "env_var": "GROQ_API_KEY",
    },
    "DISCORD_BOT_TOKEN": {
        "name": "Discord Bot Token",
        "description": "Bot token for Discord integration",
        "usage_description": "Your Discord bot token from the developer portal",
        "secret": True, "public": False, "required": False,
        "depends_on": [], "validation_method": "discord",
        "type": "token", "env_var": "DISCORD_BOT_TOKEN",
    },
    "TELEGRAM_BOT_TOKEN": {
        "name": "Telegram Bot Token",
        "description": "Bot token for Telegram integration",
        "usage_description": "Your Telegram bot token from @BotFather",
        "secret": True, "public": False, "required": False,
        "depends_on": [], "validation_method": "telegram",
        "type": "token", "env_var": "TELEGRAM_BOT_TOKEN",
    },
}


# ============================================================================
# Utility Functions
# ============================================================================

def get_unconfigured_required(config: OnboardingConfig) -> List[Tuple[str, OnboardingSetting]]:
    """Get unconfigured required settings."""
    return [
        (key, setting)
        for key, setting in config.settings.items()
        if setting.required and setting.value is None
    ]


def get_unconfigured_optional(config: OnboardingConfig) -> List[Tuple[str, OnboardingSetting]]:
    """Get unconfigured optional settings."""
    return [
        (key, setting)
        for key, setting in config.settings.items()
        if not setting.required and setting.value is None
    ]


def is_onboarding_complete(config: OnboardingConfig) -> bool:
    """Check if all required settings are configured."""
    return len(get_unconfigured_required(config)) == 0


def get_next_setting(config: OnboardingConfig) -> Optional[Tuple[str, OnboardingSetting]]:
    """Get the next setting to configure, respecting dependencies."""
    for key, setting in get_unconfigured_required(config):
        deps_met = all(
            config.settings.get(dep) and config.settings[dep].value is not None
            for dep in setting.depends_on
        )
        if deps_met:
            return (key, setting)

    for key, setting in get_unconfigured_optional(config):
        deps_met = all(
            config.settings.get(dep) and config.settings[dep].value is not None
            for dep in setting.depends_on
        )
        if deps_met:
            return (key, setting)

    return None


def create_onboarding_config(
    required_keys: List[str],
    optional_keys: Optional[List[str]] = None,
    custom_settings: Optional[Dict[str, Dict[str, Any]]] = None,
) -> OnboardingConfig:
    """Create an onboarding config from lists of required/optional secret keys."""
    optional_keys = optional_keys or []
    custom_settings = custom_settings or {}
    settings: Dict[str, OnboardingSetting] = {}

    for key in required_keys:
        common = COMMON_API_KEY_SETTINGS.get(key, {})
        custom = custom_settings.get(key, {})
        merged = {**common, **custom}
        settings[key] = OnboardingSetting(
            name=merged.get("name", key),
            description=merged.get("description", f"Configure {key}"),
            usage_description=merged.get("usage_description"),
            secret=merged.get("secret", True),
            public=merged.get("public", False),
            required=True,
            depends_on=merged.get("depends_on", []),
            validation_method=merged.get("validation_method"),
            type=merged.get("type", "api_key"),
            env_var=merged.get("env_var", key),
            value=None,
        )

    for key in optional_keys:
        common = COMMON_API_KEY_SETTINGS.get(key, {})
        custom = custom_settings.get(key, {})
        merged = {**common, **custom}
        settings[key] = OnboardingSetting(
            name=merged.get("name", key),
            description=merged.get("description", f"Configure {key}"),
            usage_description=merged.get("usage_description"),
            secret=merged.get("secret", True),
            public=merged.get("public", False),
            required=False,
            depends_on=merged.get("depends_on", []),
            validation_method=merged.get("validation_method"),
            type=merged.get("type", "api_key"),
            env_var=merged.get("env_var", key),
            value=None,
        )

    return OnboardingConfig(settings=settings)


# ============================================================================
# Onboarding Service
# ============================================================================

class OnboardingService:
    """
    Onboarding service for conversational secret collection.

    Manages the lifecycle of onboarding flows:
    - Detects which secrets are missing
    - Creates guided step-by-step flows
    - Processes user input and validates
    - Stores collected secrets via the SecretsService
    """

    def __init__(self, runtime: Any):
        self._runtime = runtime
        self._secrets_service = None
        self._sessions: Dict[str, Dict[str, Any]] = {}

    async def start(self) -> None:
        """Start the onboarding service."""
        logger.info("[OnboardingService] Starting")
        from .service import SecretsService
        self._secrets_service = self._runtime.get_service(SecretsService.service_type)
        logger.info("[OnboardingService] Started")

    async def stop(self) -> None:
        """Stop the onboarding service."""
        logger.info("[OnboardingService] Stopping")
        self._sessions.clear()
        logger.info("[OnboardingService] Stopped")

    def detect_missing_secrets(
        self,
        runtime: Any,
        required_keys: List[str],
    ) -> List[str]:
        """
        Detect which required secrets are missing from the runtime.

        Checks character.settings.secrets and environment variables.

        Args:
            runtime: The agent runtime
            required_keys: List of secret keys that are required

        Returns:
            List of missing secret keys
        """
        missing = []
        import os

        for key in required_keys:
            # Check runtime settings
            value = None
            character = getattr(runtime, "character", None)
            if character:
                settings = getattr(character, "settings", None)
                if isinstance(settings, dict):
                    secrets = settings.get("secrets", {})
                    if isinstance(secrets, dict) and key in secrets:
                        stored = secrets[key]
                        if isinstance(stored, str) and stored:
                            value = stored
                        elif isinstance(stored, dict) and stored.get("value"):
                            value = stored["value"]

            # Fallback to environment variable
            if value is None:
                value = os.environ.get(key)

            # Also check get_setting if available
            if value is None and hasattr(runtime, "get_setting"):
                value = runtime.get_setting(key)

            if not value:
                missing.append(key)

        return missing

    def create_onboarding_flow(
        self,
        runtime: Any,
        missing_secrets: List[str],
        custom_settings: Optional[Dict[str, Dict[str, Any]]] = None,
    ) -> List[OnboardingFlowStep]:
        """
        Create an onboarding flow for the given missing secrets.

        Args:
            runtime: The agent runtime
            missing_secrets: List of missing secret keys
            custom_settings: Optional custom setting overrides

        Returns:
            Ordered list of onboarding flow steps
        """
        custom_settings = custom_settings or {}
        steps: List[OnboardingFlowStep] = []
        total = len(missing_secrets)

        for i, key in enumerate(missing_secrets):
            common = COMMON_API_KEY_SETTINGS.get(key, {})
            custom = custom_settings.get(key, {})
            merged = {**common, **custom}

            setting = OnboardingSetting(
                name=merged.get("name", key),
                description=merged.get("description", f"Configure {key}"),
                usage_description=merged.get("usage_description"),
                secret=merged.get("secret", True),
                public=merged.get("public", False),
                required=merged.get("required", True),
                depends_on=merged.get("depends_on", []),
                validation_method=merged.get("validation_method"),
                type=merged.get("type", "api_key"),
                env_var=merged.get("env_var", key),
                value=None,
            )

            prompt = DEFAULT_ONBOARDING_MESSAGES["ask_setting"].format(
                setting_name=setting.name,
                usage_description=setting.usage_description or setting.description,
            )

            steps.append(OnboardingFlowStep(
                key=key,
                setting=setting,
                prompt=prompt,
                step_number=i + 1,
                total_steps=total,
                is_required=setting.required,
            ))

        return steps

    async def process_onboarding_step(
        self,
        runtime: Any,
        step: OnboardingFlowStep,
        user_input: str,
    ) -> OnboardingStepResult:
        """
        Process user input for a specific onboarding step.

        Validates the input, stores the secret, and determines next step.

        Args:
            runtime: The agent runtime
            step: The current onboarding step
            user_input: The user's input value

        Returns:
            Result indicating success, next step, or completion
        """
        value = user_input.strip()
        if not value:
            return OnboardingStepResult(
                success=False,
                error=f"Please provide a value for {step.setting.name}.",
                message=DEFAULT_ONBOARDING_MESSAGES["error"],
            )

        # Validate if validation method exists
        if step.setting.validation_method:
            from .validation import validate_secret
            result = await validate_secret(step.key, value, step.setting.validation_method)
            if not result.is_valid:
                return OnboardingStepResult(
                    success=False,
                    error=result.error,
                    message=f"That doesn't look like a valid {step.setting.name}. "
                            f"{step.setting.usage_description or 'Please try again.'}",
                )

        # Store the secret
        if self._secrets_service:
            context = SecretContext(
                level=SecretLevel.GLOBAL,
                agent_id=runtime.agent_id,
            )
            await self._secrets_service.set(step.key, value, context, {
                "description": step.setting.description,
                "type": step.setting.type or "api_key",
                "encrypted": step.setting.secret,
            })

        updated_message = DEFAULT_ONBOARDING_MESSAGES["setting_updated"].format(
            setting_name=step.setting.name,
        )

        # Check if this was the last step
        if step.step_number >= step.total_steps:
            return OnboardingStepResult(
                success=True,
                complete=True,
                updated_key=step.key,
                message=f"{updated_message}\n\n{DEFAULT_ONBOARDING_MESSAGES['all_complete']}",
            )

        return OnboardingStepResult(
            success=True,
            complete=False,
            updated_key=step.key,
            message=updated_message,
        )

    async def get_onboarding_status(self, world_id: str) -> Dict[str, Any]:
        """Get the onboarding status for a world."""
        world = await self._runtime.get_world(world_id)

        if not world or not hasattr(world, "metadata") or not isinstance(world.metadata, dict):
            return {
                "initialized": False,
                "complete": False,
                "configured_count": 0,
                "required_count": 0,
                "missing_required": [],
            }

        settings = world.metadata.get("settings", {})
        if not settings:
            return {
                "initialized": False,
                "complete": False,
                "configured_count": 0,
                "required_count": 0,
                "missing_required": [],
            }

        required = [(k, s) for k, s in settings.items() if s.get("required")]
        configured = [(k, s) for k, s in required if s.get("value") is not None]
        missing = [k for k, s in required if s.get("value") is None]

        return {
            "initialized": True,
            "complete": len(missing) == 0,
            "configured_count": len(configured),
            "required_count": len(required),
            "missing_required": missing,
        }

    def generate_settings_context(
        self,
        config: OnboardingConfig,
        is_onboarding: bool,
        agent_name: str,
    ) -> str:
        """Generate the settings provider context for LLM."""
        entries = list(config.settings.items())
        unconfigured = get_unconfigured_required(config)

        settings_list = []
        for key, setting in entries:
            status = "Configured" if setting.value is not None else "Not set"
            required = "(Required)" if setting.required else "(Optional)"
            if setting.secret and setting.value:
                display_value = "****************"
            else:
                display_value = setting.value or "Not set"

            settings_list.append(
                f"{key}: {display_value} {required}\n"
                f"({setting.name}) {setting.usage_description or setting.description}"
            )

        formatted = "\n\n".join(settings_list)
        valid_keys = f"Valid setting keys: {', '.join(k for k, _ in entries)}"

        if is_onboarding and unconfigured:
            return (
                f"# PRIORITY TASK: Onboarding\n\n"
                f"{agent_name} needs to help the user configure "
                f"{len(unconfigured)} required settings:\n\n"
                f"{formatted}\n\n{valid_keys}\n\n"
                f"Instructions for {agent_name}:\n"
                f"- Only update settings if the user is clearly responding to a "
                f"setting you are currently asking about.\n"
                f"- If the user's reply clearly maps to a setting and a valid value, "
                f"you **must** call the UPDATE_SETTINGS action.\n"
                f"- Never hallucinate settings or respond with values not listed above.\n"
                f"- Prioritize configuring required settings before optional ones."
            )

        warning = ""
        if unconfigured:
            warning = (
                f"IMPORTANT: {len(unconfigured)} required settings still need configuration.\n\n"
            )
        else:
            warning = "All required settings are configured.\n\n"

        return f"## Current Configuration\n{warning}{formatted}"
