"""
LLM provider routing for task-type-based provider selection.

This module provides routing logic to select appropriate LLM providers
based on task type, with fallback to default providers.

Requirements: INT-08 (provider routing based on task type)
"""

from __future__ import annotations

import logging
from typing import Any

logger = logging.getLogger(__name__)

# Task type to provider mapping
# Maps task types to preferred providers based on task characteristics:
# - planning: Complex reasoning, needs capable model
# - execution: Fast local execution, many parallel tasks
# - verification: Balanced performance for validation
# - research: Web search capable, general purpose
TASK_PROVIDER_MAP: dict[str, str] = {
    "planning": "claude",  # Complex reasoning, chain-of-thought
    "execution": "ollama",  # Fast local, many parallel tasks
    "verification": "sonnet",  # Balanced, good for validation
    "research": "sonnet",  # Web search capable
    "generation": "claude",  # Creative generation
    "analysis": "sonnet",  # Code analysis
    "review": "sonnet",  # Code review
    "documentation": "ollama",  # Documentation generation
    "testing": "ollama",  # Test generation
    "refactoring": "sonnet",  # Code refactoring
}

# Default providers when task type is unknown
DEFAULT_PROVIDERS: dict[str, str] = {
    "primary": "ollama",  # Local-first default
    "fallback": "claude",  # Cloud fallback
}


def route_to_provider(task_type: str, config: dict[str, Any] | None = None) -> str:
    """Route a task type to the appropriate LLM provider.

    Looks up the task type in TASK_PROVIDER_MAP. If not found,
    falls back to the default provider from config or the system default.

    Args:
        task_type: Type of task (e.g., "planning", "execution", "verification")
        config: Optional configuration with:
            - default_provider: Override default provider
            - provider_overrides: Dict of task_type -> provider overrides

    Returns:
        Provider name (e.g., "ollama", "claude", "sonnet")

    Example:
        >>> route_to_provider("planning")
        'claude'
        >>> route_to_provider("execution")
        'ollama'
        >>> route_to_provider("unknown", {"default_provider": "sonnet"})
        'sonnet'
    """
    config = config or {}

    # Check for provider overrides in config
    overrides = config.get("provider_overrides", {})
    if task_type in overrides:
        provider = overrides[task_type]
        logger.debug(
            f"Using override provider '{provider}' for task type '{task_type}'"
        )
        return provider

    # Look up in task provider map
    if task_type in TASK_PROVIDER_MAP:
        provider = TASK_PROVIDER_MAP[task_type]
        logger.debug(f"Routing task type '{task_type}' to provider '{provider}'")
        return provider

    # Fall back to default
    default = config.get("default_provider", DEFAULT_PROVIDERS["primary"])
    logger.debug(f"Unknown task type '{task_type}', using default provider '{default}'")
    return default


def validate_provider(provider: str) -> bool:
    """Check if a provider is available.

    Attempts to import the provider module from rlm_toolkit.providers
    to verify availability.

    Args:
        provider: Provider name to check (e.g., "ollama", "claude")

    Returns:
        True if provider is available, False otherwise

    Example:
        >>> validate_provider("ollama")
        True
        >>> validate_provider("nonexistent")
        False
    """
    try:
        # Map provider names to module names
        module_map = {
            "ollama": "rlm_toolkit.providers.ollama",
            "claude": "rlm_toolkit.providers.anthropic",
            "sonnet": "rlm_toolkit.providers.anthropic",
            "openai": "rlm_toolkit.providers.openai",
            "anthropic": "rlm_toolkit.providers.anthropic",
        }

        module_name = module_map.get(provider.lower())
        if not module_name:
            logger.debug(f"Unknown provider '{provider}'")
            return False

        # Try to import the module
        import importlib

        importlib.import_module(module_name)
        logger.debug(f"Provider '{provider}' is available")
        return True

    except ImportError:
        logger.debug(f"Provider '{provider}' is not available (import failed)")
        return False
    except Exception as e:
        logger.warning(f"Error checking provider '{provider}': {e}")
        return False


def get_provider_config(
    provider: str, config: dict[str, Any] | None = None
) -> dict[str, Any]:
    """Get provider-specific configuration.

    Returns configuration settings for initializing an LLM client
    for the specified provider.

    Args:
        provider: Provider name (e.g., "ollama", "claude")
        config: Optional base configuration to merge with provider defaults

    Returns:
        Configuration dictionary for the provider

    Example:
        >>> get_provider_config("ollama")
        {'model': 'llama3.2', 'base_url': 'http://localhost:11434'}
        >>> get_provider_config("claude")
        {'model': 'claude-3-5-sonnet-20241022'}
    """
    config = config or {}

    # Provider-specific default configurations
    provider_defaults: dict[str, dict[str, Any]] = {
        "ollama": {
            "model": "llama3.2",
            "base_url": "http://localhost:11434",
            "timeout": 120,
        },
        "claude": {
            "model": "claude-3-5-sonnet-20241022",
            "max_tokens": 4096,
            "temperature": 0.7,
        },
        "sonnet": {
            "model": "claude-3-5-sonnet-20241022",
            "max_tokens": 4096,
            "temperature": 0.7,
        },
        "openai": {
            "model": "gpt-4o",
            "max_tokens": 4096,
            "temperature": 0.7,
        },
        "anthropic": {
            "model": "claude-3-5-sonnet-20241022",
            "max_tokens": 4096,
            "temperature": 0.7,
        },
    }

    # Get defaults for this provider
    defaults = provider_defaults.get(provider.lower(), {})

    # Merge with provided config (config values take precedence)
    result = {**defaults, **config}

    # Add provider name to result
    result["provider"] = provider.lower()

    logger.debug(f"Provider config for '{provider}': {result}")
    return result


def get_fallback_provider(task_type: str, failed_provider: str) -> str | None:
    """Get a fallback provider when the primary fails.

    Returns an alternative provider based on the task type and
    the provider that failed.

    Args:
        task_type: Type of task
        failed_provider: Provider that failed

    Returns:
        Fallback provider name, or None if no fallback available

    Example:
        >>> get_fallback_provider("planning", "claude")
        'sonnet'
        >>> get_fallback_provider("execution", "ollama")
        'sonnet'
    """
    # Fallback chain: ollama -> sonnet -> claude
    fallback_chains: dict[str, list[str]] = {
        "ollama": ["sonnet", "claude"],
        "sonnet": ["claude", "ollama"],
        "claude": ["sonnet", "ollama"],
        "openai": ["claude", "sonnet"],
    }

    chain = fallback_chains.get(failed_provider.lower(), [])
    for fallback in chain:
        if validate_provider(fallback):
            logger.info(
                f"Using fallback provider '{fallback}' for task '{task_type}' "
                f"after '{failed_provider}' failed"
            )
            return fallback

    logger.warning(f"No fallback provider available for '{failed_provider}'")
    return None


def list_available_providers() -> list[str]:
    """List all available providers.

    Checks each known provider and returns those that are available.

    Returns:
        List of available provider names

    Example:
        >>> list_available_providers()
        ['ollama', 'claude']
    """
    known_providers = ["ollama", "claude", "sonnet", "openai"]
    available = []

    for provider in known_providers:
        if validate_provider(provider):
            available.append(provider)

    logger.info(f"Available providers: {available}")
    return available


def get_task_provider_map() -> dict[str, str]:
    """Get a copy of the task provider mapping.

    Returns:
        Copy of TASK_PROVIDER_MAP

    Example:
        >>> mapping = get_task_provider_map()
        >>> mapping["planning"]
        'claude'
    """
    return TASK_PROVIDER_MAP.copy()
