"""
Configuration builder for Claude Code runtime.

This module handles the construction of ClaudeAgentOptions from execution
context, including LiteLLM integration, MCP servers, and session management.

BUG FIX #4: Added session_id validation before use.
"""

from typing import Dict, Any, Tuple, Optional, Callable, Set, List
import structlog
import os
import asyncio

from .tool_mapper import map_skills_to_tools, validate_tool_names
from .mcp_builder import build_mcp_servers
from .hooks import build_hooks
from .mcp_discovery import discover_all_mcp_resources

logger = structlog.get_logger(__name__)

# Note: Claude SDK handles MCP discovery automatically - we verify connection and log errors


def build_mcp_permission_handler(
    mcp_servers: Dict[str, Any], allowed_tools: List[str]
) -> Callable:
    """
    Build permission handler for MCP tools.

    IMPORTANT: The SDK discovers MCP tools automatically, but does NOT
    automatically grant permission to use them. We need to handle permissions
    separately using the canUseTool callback.

    This handler:
    1. Allows tools in the allowed_tools list (builtin tools)
    2. Auto-allows tools matching mcp__<server_name>__* for configured servers
    3. Denies everything else

    Args:
        mcp_servers: Dict of MCP server configurations
        allowed_tools: List of allowed builtin tool names

    Returns:
        Async permission handler for canUseTool parameter
    """
    # Extract MCP server names for permission matching
    mcp_server_names: Set[str] = set(mcp_servers.keys())

    logger.info(
        "building_mcp_permission_handler",
        mcp_server_count=len(mcp_server_names),
        mcp_server_names=list(mcp_server_names),
        builtin_tools_count=len(allowed_tools),
        note="SDK discovers tools, but we handle permissions via canUseTool"
    )

    async def permission_handler(
        tool_name: str, input_data: dict, context: dict
    ) -> Dict[str, Any]:
        """
        Permission handler that allows:
        1. Builtin tools from allowed_tools list
        2. MCP tools matching mcp__<server_name>__* pattern

        Args:
            tool_name: Tool being invoked
            input_data: Tool input parameters
            context: Execution context

        Returns:
            Permission decision: {"behavior": "allow"|"deny", "updatedInput": input_data}
        """
        # ALWAYS log permission checks for debugging
        logger.info(
            "permission_handler_called",
            tool_name=tool_name,
            is_builtin=tool_name in allowed_tools,
            is_mcp=tool_name.startswith("mcp__"),
            configured_servers=list(mcp_server_names),
        )

        # Allow builtin tools
        if tool_name in allowed_tools:
            logger.info("permission_granted_builtin", tool_name=tool_name)
            return {"behavior": "allow", "updatedInput": input_data}

        # Allow MCP tools from configured servers
        # Pattern: mcp__<server_name>__<tool_name>
        if tool_name.startswith("mcp__"):
            parts = tool_name.split("__", 2)
            if len(parts) >= 2:
                server_name = parts[1]
                logger.info(
                    "checking_mcp_permission",
                    tool_name=tool_name,
                    extracted_server=server_name,
                    configured_servers=list(mcp_server_names),
                    matches=server_name in mcp_server_names,
                )
                if server_name in mcp_server_names:
                    logger.info(
                        "permission_granted_mcp",
                        tool_name=tool_name,
                        server_name=server_name,
                    )
                    return {"behavior": "allow", "updatedInput": input_data}

        # Deny unrecognized tools
        logger.warning(
            "tool_permission_denied",
            tool_name=tool_name,
            reason="not_in_allowed_tools_or_mcp_servers",
            available_mcp_servers=list(mcp_server_names),
            available_builtin_count=len(allowed_tools),
        )
        return {
            "behavior": "deny",
            "updatedInput": input_data,
            "message": f"Tool '{tool_name}' not permitted. Not in allowed tools or MCP servers."
        }

    return permission_handler


def validate_session_id(session_id: Optional[str]) -> Optional[str]:
    """
    Validate session_id format before use.

    BUG FIX #4: Ensures session_id is valid before storing for multi-turn.

    Args:
        session_id: Session ID to validate

    Returns:
        Valid session_id or None if invalid
    """
    if not session_id:
        return None

    if not isinstance(session_id, str) or len(session_id) < 10:
        logger.warning(
            "invalid_session_id_format",
            session_id=session_id if isinstance(session_id, str) else None,
            type=type(session_id).__name__,
            length=len(session_id) if isinstance(session_id, str) else 0,
        )
        return None

    return session_id


async def build_claude_options(
    context: Any,  # RuntimeExecutionContext
    event_callback: Optional[Callable] = None,
    runtime: Optional[Any] = None,  # ClaudeCodeRuntime instance for caching
) -> Tuple[Any, Dict[str, str]]:
    """
    Build ClaudeAgentOptions from execution context.

    Args:
        context: RuntimeExecutionContext with prompt, history, config
        event_callback: Optional event callback for hooks
        runtime: Optional ClaudeCodeRuntime instance for MCP discovery caching

    Returns:
        Tuple of (ClaudeAgentOptions instance, active_tools dict)
    """
    from claude_agent_sdk import ClaudeAgentOptions

    # Extract configuration
    agent_config = context.agent_config or {}
    runtime_config = context.runtime_config or {}

    # Get LiteLLM configuration (same as DefaultRuntime/Agno)
    litellm_api_base = os.getenv("LITELLM_API_BASE", "https://llm-proxy.kubiya.ai")
    litellm_api_key = os.getenv("LITELLM_API_KEY")

    if not litellm_api_key:
        raise ValueError("LITELLM_API_KEY environment variable not set")

    # Determine model (use LiteLLM format)
    model = context.model_id or os.environ.get(
        "LITELLM_DEFAULT_MODEL", "kubiya/claude-sonnet-4"
    )

    # Map skills to Claude Code tool names (built-in tools only)
    allowed_tools = map_skills_to_tools(context.skills)

    # Build MCP servers (both from context and custom skills)
    # SDK will discover tools automatically - we just provide configs
    mcp_servers, _ = build_mcp_servers(
        context.skills, context.mcp_servers
    )

    # Verify MCP server connections and discover tools
    # This helps us detect configuration errors early
    mcp_discovery_results = {}
    if mcp_servers:
        try:
            logger.info(
                "verifying_mcp_server_connections",
                server_count=len(mcp_servers),
                server_names=list(mcp_servers.keys()),
                note="Attempting to connect and discover tools from all MCP servers"
            )
            mcp_discovery_results = await discover_all_mcp_resources(mcp_servers)

            # Log results for each server
            failed_servers = []
            successful_servers = []
            skipped_servers = []
            for server_name, result in mcp_discovery_results.items():
                # Check if server was skipped (HTTP servers use native SDK discovery)
                if result.get("skipped"):
                    skipped_servers.append(server_name)
                    logger.info(
                        "mcp_server_skipped_native_discovery",
                        server_name=server_name,
                        status="⚡ HTTP - Using SDK native discovery",
                        note="Pre-discovery skipped for HTTP servers (SDK handles them natively)"
                    )
                elif result["connected"]:
                    tool_count = len(result["tools"])
                    successful_servers.append(server_name)
                    if tool_count == 0:
                        logger.warning(
                            "mcp_server_connected_but_no_tools",
                            server_name=server_name,
                            message=f"MCP server '{server_name}' connected successfully but discovered 0 tools",
                            recommendation="Check server implementation - it may not be exposing any tools"
                        )
                    else:
                        logger.info(
                            "mcp_server_verified",
                            server_name=server_name,
                            tool_count=tool_count,
                            tool_names=result["tools"][:5] if tool_count <= 5 else [t["name"] for t in result["tools"][:5]] + [f"... and {tool_count - 5} more"],
                            status="✅ Connected and discovered tools"
                        )
                else:
                    failed_servers.append(server_name)
                    logger.error(
                        "mcp_server_connection_failed",
                        server_name=server_name,
                        error=result.get("error", "Unknown error"),
                        status="❌ Failed to connect",
                        recommendation="Check server command, args, and environment variables in agent configuration"
                    )

            # Summary log
            if failed_servers:
                logger.error(
                    "mcp_verification_summary",
                    total_servers=len(mcp_servers),
                    http_skipped=len(skipped_servers),
                    successful=len(successful_servers),
                    failed=len(failed_servers),
                    failed_server_names=failed_servers,
                    message=f"⚠️  {len(failed_servers)} MCP server(s) failed to connect - agent may not have access to expected tools"
                )
            else:
                logger.info(
                    "mcp_verification_summary",
                    total_servers=len(mcp_servers),
                    http_skipped=len(skipped_servers),
                    successful=len(successful_servers),
                    total_tools_discovered=sum(len(r["tools"]) for r in mcp_discovery_results.values() if not r.get("skipped")),
                    message=f"✅ All MCP servers ready: {len(skipped_servers)} HTTP (native SDK) + {len(successful_servers)} pre-discovered"
                )

        except Exception as discovery_error:
            logger.error(
                "mcp_discovery_process_failed",
                error=str(discovery_error),
                error_type=type(discovery_error).__name__,
                message="Failed to verify MCP server connections - will proceed but tools may not be available",
                exc_info=True
            )

    # BUG FIX #6: Validate built-in tool names before using
    allowed_tools, invalid_tools = validate_tool_names(allowed_tools)

    # Build permission handler for MCP tools
    # IMPORTANT: SDK discovers tools, but we must permit them via canUseTool
    permission_handler = None
    if mcp_servers:
        permission_handler = build_mcp_permission_handler(mcp_servers, allowed_tools)
        logger.info(
            "mcp_permission_handler_configured",
            mcp_servers=list(mcp_servers.keys()),
            note="Will auto-allow tools matching mcp__<server_name>__* pattern"
        )

    logger.info(
        "claude_code_tools_configured",
        builtin_tools_count=len(allowed_tools),
        mcp_servers_count=len(mcp_servers),
        mcp_server_names=list(mcp_servers.keys()) if mcp_servers else [],
        builtin_tools=allowed_tools[:20],  # Limit for readability
        has_permission_handler=permission_handler is not None,
        note="SDK discovers MCP tools automatically, we handle permissions via canUseTool"
    )

    # Create shared active_tools dict for tool name tracking
    # This is populated in the stream when ToolUseBlock is received,
    # and used in hooks to look up tool names
    active_tools: Dict[str, str] = {}

    # Create shared completed_tools set for tracking published completion events
    # This prevents duplicate tool_complete events from hooks and ToolResultBlock
    from typing import Set
    completed_tools: Set[str] = set()

    # Build hooks for tool execution monitoring
    hooks = (
        build_hooks(context.execution_id, event_callback, active_tools, completed_tools)
        if event_callback
        else {}
    )

    # Build environment with LiteLLM configuration
    env = runtime_config.get("env", {}).copy()

    # Configure Claude Code SDK to use LiteLLM proxy
    env["ANTHROPIC_BASE_URL"] = litellm_api_base
    env["ANTHROPIC_API_KEY"] = litellm_api_key

    # Pass Kubiya API credentials for workflow execution
    kubiya_api_key = os.environ.get("KUBIYA_API_KEY")
    if kubiya_api_key:
        env["KUBIYA_API_KEY"] = kubiya_api_key
        logger.debug("added_kubiya_api_key_to_environment")

    kubiya_api_base = os.environ.get("KUBIYA_API_BASE")
    if kubiya_api_base:
        env["KUBIYA_API_BASE"] = kubiya_api_base
        logger.debug(
            "added_kubiya_api_base_to_environment", kubiya_api_base=kubiya_api_base
        )

    # Get session_id from previous turn for conversation continuity
    # BUG FIX #4: Validate session_id format before use
    previous_session_id = None
    if context.user_metadata:
        raw_session_id = context.user_metadata.get("claude_code_session_id")
        previous_session_id = validate_session_id(raw_session_id)

        if raw_session_id and not previous_session_id:
            logger.warning(
                "invalid_session_id_from_user_metadata",
                raw_session_id=raw_session_id,
            )

    logger.info(
        "building_claude_code_options",
        has_user_metadata=bool(context.user_metadata),
        has_session_id_in_metadata=bool(previous_session_id),
        previous_session_id_prefix=(
            previous_session_id[:16] if previous_session_id else None
        ),
        will_resume=bool(previous_session_id),
    )

    # NEW: Support native subagents for team execution
    sdk_agents = None
    agents_config = agent_config.get('runtime_config', {}).get('agents')

    if agents_config:
        from claude_agent_sdk import AgentDefinition

        sdk_agents = {}
        for agent_id, agent_data in agents_config.items():
            sdk_agents[agent_id] = AgentDefinition(
                description=agent_data.get('description', ''),
                prompt=agent_data.get('prompt', ''),
                tools=agent_data.get('tools'),
                model=agent_data.get('model', 'inherit'),
            )

        logger.info(
            "native_subagents_configured",
            execution_id=context.execution_id[:8] if context.execution_id else "unknown",
            subagent_count=len(sdk_agents),
            subagent_ids=list(sdk_agents.keys()),
            subagent_models=[agent_data.get('model', 'inherit') for agent_data in agents_config.values()],
        )

    # Log detailed MCP server configuration for debugging
    if mcp_servers:
        logger.info(
            "mcp_servers_being_passed_to_sdk",
            server_count=len(mcp_servers),
            server_names=list(mcp_servers.keys()),
            server_configs={
                name: {
                    "type": cfg.get("type", "stdio"),
                    "url": cfg.get("url", "N/A")[:50] if cfg.get("url") else "N/A",
                    "command": cfg.get("command", "N/A"),
                    "args": cfg.get("args", []),  # Show args for debugging
                    "has_env": bool(cfg.get("env"))
                }
                for name, cfg in mcp_servers.items()
            },
            note="SDK should discover tools from these servers"
        )

    # Build options - SDK discovers tools, we handle permissions
    options_dict = {
        "system_prompt": context.system_prompt or "",
        "allowed_tools": allowed_tools,
        "mcp_servers": mcp_servers,  # SDK discovers tools automatically
        "agents": sdk_agents,  # NEW: Native subagent support for teams
        "permission_mode": runtime_config.get(
            "permission_mode",
            os.getenv("CLAUDE_CODE_PERMISSION_MODE", "bypassPermissions")
        ),
        "cwd": agent_config.get("cwd") or runtime_config.get("cwd"),
        "model": model,
        "env": env,
        "max_turns": runtime_config.get("max_turns", 25),  # Default 25 turns for complex multi-step workflows
        "hooks": hooks,
        "setting_sources": [],  # Explicit: don't load filesystem settings
        "include_partial_messages": True,  # Enable character-by-character streaming
        "resume": previous_session_id,  # Resume previous conversation if available
    }

    # Add permission handler if we have MCP servers
    # CRITICAL: SDK discovers tools but doesn't auto-permit them
    if permission_handler:
        options_dict["can_use_tool"] = permission_handler

    options = ClaudeAgentOptions(**options_dict)

    logger.info(
        "claude_code_options_configured",
        include_partial_messages=getattr(options, "include_partial_messages", "NOT SET"),
        permission_mode=options.permission_mode,
        model=options.model,
        mcp_servers_configured=len(mcp_servers) if mcp_servers else 0,
        has_can_use_tool=permission_handler is not None,
        note="SDK discovers tools, canUseTool handler permits mcp__<server>__* pattern"
    )

    # Return options, active_tools dict, and completed_tools set for tracking
    return options, active_tools, completed_tools
