import json
import logging
import importlib
import os
from typing import List, Dict, Any, Optional
from pydantic import ValidationError
from src.services.provider.registry import ModelRegistry, ModelType, ServiceConfig, ServiceFactory

logger = logging.getLogger(__name__)

# 密钥s that are reserved at the type level and should not be treated as model IDs
RESERVED_KEYS = ["defaults", "kwargs", "base_url"]

def load_services_from_config(config_path: str):
    """
    Load services from a configuration file or a directory containing JSON files.
    
    Supports two directory layouts:
    1. Flat: services/*.json (one file per provider, e.g. dashscope.json)
    2. Split: services/<provider>/ with provider.json + llm.json/image.json/video.json/audio.json
    
    When both exist for the same provider, the split directory takes priority.
    """
    if not os.path.exists(config_path):
        logger.error(f"Service config path not found: {config_path}")
        return

    # It's a directory, load all .json files in it
    if os.path.isdir(config_path):
        logger.info(f"Loading services from directory: {config_path}")
        
        # Discover provider subdirectories (those with provider.json)
        provider_dirs = set()
        for entry in os.listdir(config_path):
            entry_path = os.path.join(config_path, entry)
            if os.path.isdir(entry_path) and os.path.exists(os.path.join(entry_path, "provider.json")):
                provider_dirs.add(entry)
        
        # Load provider subdirectories first
        for prov_name in sorted(provider_dirs):
            prov_dir = os.path.join(config_path, prov_name)
            _load_provider_directory(prov_dir)
        
        # Then load flat .json files (skip if provider dir exists with same name)
        for filename in sorted(os.listdir(config_path)):
            if not filename.endswith(".json") or filename == "default.json":
                continue
            basename = filename[:-5]  # strip .json
            if basename in provider_dirs:
                logger.debug(f"Skipping flat file {filename} (provider directory exists)")
                continue
            # Note: common.json is a valid service file, so we don't skip it anymore
            file_path = os.path.join(config_path, filename)
            if os.path.isfile(file_path):
                _load_single_config_file(file_path)
        
        # Then load defaults if default.json exists
        default_config_path = os.path.join(config_path, "default.json")
        if os.path.exists(default_config_path):
            _load_defaults(default_config_path)
        
        # Also load user_config.json for user-customized defaults (higher priority)
        user_config_path = os.path.join(os.path.dirname(config_path), "user_config.json")
        if os.path.exists(user_config_path):
            _load_user_defaults(user_config_path)
    else:
        # It's a single file
        _load_single_config_file(config_path)


def _load_provider_directory(prov_dir: str):
    """
    Load a provider from a split directory structure:
      <provider>/
        provider.json   - provider metadata (id, name, api_key, etc.)
        llm.json        - LLM models (optional)
        image.json      - Image models (optional)
        video.json      - Video models (optional)
        audio.json      - Audio models (optional)
        music.json      - Music models (optional)
    """
    prov_name = os.path.basename(prov_dir)
    provider_json_path = os.path.join(prov_dir, "provider.json")
    
    try:
        with open(provider_json_path, 'r', encoding='utf-8') as f:
            provider_meta = json.load(f)
    except Exception as e:
        logger.error(f"Failed to load provider.json from {prov_dir}: {e}")
        return
    
    provider_id = provider_meta.get("id", prov_name)
    
    # Register provider metadata
    ModelRegistry.register_provider_metadata(provider_id, provider_meta)
    
    # Type mapping: filename (without .json) -> service type
    type_files = ["llm", "image", "video", "audio", "lyrics", "music", "upscale"]
    services_loaded = 0
    
    for svc_type in type_files:
        type_file = os.path.join(prov_dir, f"{svc_type}.json")
        if not os.path.exists(type_file):
            continue
        
        try:
            with open(type_file, 'r', encoding='utf-8') as f:
                services_map = json.load(f)
        except Exception as e:
            logger.error(f"Failed to load {type_file}: {e}")
            continue
        
        if not isinstance(services_map, dict):
            logger.warning(f"Invalid format in {type_file}, expected dict")
            continue
        
        # 提取 type-level defaults (non-dict values like base_url)
        type_defaults = {}
        for k, v in services_map.items():
            if k in RESERVED_KEYS or not isinstance(v, dict):
                type_defaults[k] = v
        
        # 进程 each model in this type file
        for svc_id, svc_cfg in services_map.items():
            if svc_id in RESERVED_KEYS or not isinstance(svc_cfg, dict):
                continue
            
            new_cfg = _normalize_service_config(
                svc_id, svc_cfg, svc_type, type_defaults, provider_meta
            )
            register_service(new_cfg)
            services_loaded += 1
    
    logger.info(f"Loaded {services_loaded} services from provider directory: {prov_name}/")


def _load_user_defaults(file_path: str):
    """Load user-customized default models from user_config.json"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            user_config = json.load(f)
            
        logger.info(f"Loading user defaults from {file_path}...")
        
        # 映射ping from config keys to ModelType
        config_key_to_type = {
            'defaultImageModel': ModelType.IMAGE,
            'defaultVideoModel': ModelType.VIDEO,
            'defaultAudioModel': ModelType.AUDIO,
            'defaultLyricsModel': ModelType.LYRICS,
            'defaultMusicModel': ModelType.MUSIC,
            'defaultLLMModel': ModelType.LLM
        }
        
        for config_key, model_type in config_key_to_type.items():
            model_id = user_config.get(config_key)
            if model_id:
                try:
                    ModelRegistry.set_default_by_id(model_type, model_id)
                    logger.info(f"Set user default {model_type.value} model to: {model_id}")
                except Exception as e:
                    logger.warning(f"Failed to set default {model_type.value} model '{model_id}': {e}")
                    
    except Exception as e:
        logger.error(f"Failed to load user defaults from {file_path}: {e}")


def _load_defaults(file_path: str):
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            defaults = json.load(f)
            
        logger.info(f"Loading defaults from {file_path}...")
        
        for model_type_str, model_id in defaults.items():
            try:
                model_type = ModelType(model_type_str.lower())
                ModelRegistry.set_default_by_id(model_type, model_id)
            except ValueError:
                logger.warning(f"Invalid model type '{model_type_str}' in default config")
                
    except Exception as e:
        logger.error(f"Failed to load defaults from {file_path}: {e}")


def _normalize_service_config(
    svc_id: str, 
    svc_cfg: Dict[str, Any], 
    svc_type: str, 
    type_defaults: Dict[str, Any],
    provider_meta: Dict[str, Any]
) -> Dict[str, Any]:
    """ 正常ize a single service configuration by merging defaults, 
    injecting provider context, and standardizing arguments.
    
    ID Generation Strategy:
    - model_key: Original model identifier (e.g., 'qwen-image')
    - id: Unique composite ID as 'provider/model_key' (e.g., 'dashscope/qwen-image')
    
    This ensures uniqueness when multiple providers offer models with the same name.
    """
    # 创建 config with defaults
    new_cfg = svc_cfg.copy()
    
    # Apply type-level defaults
    for k, v in type_defaults.items():
        if k == "kwargs" and isinstance(v, dict):
            # 合并 kwargs: specific config overrides defaults
            default_kwargs = v.copy()
            if "kwargs" in new_cfg and isinstance(new_cfg["kwargs"], dict):
                default_kwargs.update(new_cfg["kwargs"])
                new_cfg["kwargs"] = default_kwargs
            elif "kwargs" not in new_cfg:
                new_cfg["kwargs"] = default_kwargs
        else:
            if k not in new_cfg:
                new_cfg[k] = v
    
    # Inject Provider Metadata first (needed for ID generation)
    provider_id = provider_meta.get("id")
    provider_name = provider_meta.get("name")
    
    if "provider" not in new_cfg and provider_id:
        new_cfg["provider"] = provider_id
    if "provider_name" not in new_cfg and provider_name:
        new_cfg["provider_name"] = provider_name
    
    # Store original model key (before composite ID generation)
    # This is the short name used in config (e.g., 'qwen-image')
    if "model_key" not in new_cfg:
        new_cfg["model_key"] = svc_id
    
    # 生成 unique composite ID: provider/model_key
    # This ensures uniqueness when multiple providers have same model names
    if "id" not in new_cfg:
        if provider_id:
            new_cfg["id"] = f"{provider_id}/{svc_id}"
        else:
            new_cfg["id"] = svc_id
        
    # Inject Type
    if "type" not in new_cfg:
        new_cfg["type"] = svc_type

    # API Key 从系统环境变量获取，管理员在 .env 中统一配置

    # Move specific top-level fields to kwargs for ServiceFactory compatibility
    # 字段s that are not in ServiceConfig but are arguments to the service class
    for field in ["base_url"]:
        if field in new_cfg:
            if "kwargs" not in new_cfg:
                new_cfg["kwargs"] = {}
            # Only set if not already in kwargs (priority to explicit kwargs)
            if field not in new_cfg["kwargs"]:
                new_cfg["kwargs"][field] = new_cfg.pop(field)
            else:
                # Remove from top level if duplicate
                new_cfg.pop(field)
        
    # Handle class/module split
    # If 'module' is missing but 'class' is a full path, split it
    if "module" not in new_cfg and "class" in new_cfg:
        full_class = new_cfg["class"]
        if "." in full_class:
            module_path, class_name = full_class.rsplit(".", 1)
            new_cfg["module"] = module_path
            new_cfg["class"] = class_name
            
    return new_cfg


def _load_single_config_file(file_path: str):
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)

        services_config = []

        if isinstance(data, list):
            # 遗留的 format: "services": [...]
            services_config = data
        elif isinstance(data, dict):
            # Handle new format with provider metadata
            provider_meta = data.get("provider", {})
            provider_id = provider_meta.get("id")
            
            # Register provider metadata
            if provider_id:
                ModelRegistry.register_provider_metadata(provider_id, provider_meta)
            
            if "services" in data:
                raw_services = data["services"]
                
                if isinstance(raw_services, list):
                    # 遗留的 format nested in dict
                    services_config = raw_services
                    # Inject provider info for list format
                    for svc in services_config:
                        if provider_id and "provider" not in svc:
                            svc["provider"] = provider_id
                        if provider_meta.get("name") and "provider_name" not in svc:
                            svc["provider_name"] = provider_meta.get("name")

                        # API Key 通过环境变量配置，不从 provider.json 注入
                            
                elif isinstance(raw_services, dict):
                    # New grouped format: "services": {"video": {"id": {...}}}
                    for svc_type, services_map in raw_services.items():
                        if not isinstance(services_map, dict):
                            logger.warning(f"Invalid service grouping for type {svc_type} in {file_path}")
                            continue
                        
                        # 提取 type-level defaults
                        type_defaults = {}
                        for k, v in services_map.items():
                            # Allow reserved keys even if they are dicts (e.g. kwargs)
                            if k in RESERVED_KEYS or not isinstance(v, dict):
                                type_defaults[k] = v
                            
                        for svc_id, svc_cfg in services_map.items():
                            # Skip reserved keys or non-dict items
                            if svc_id in RESERVED_KEYS or not isinstance(svc_cfg, dict):
                                continue
                                
                            # 正常ize and build config
                            new_cfg = _normalize_service_config(
                                svc_id, svc_cfg, svc_type, type_defaults, provider_meta
                            )
                            services_config.append(new_cfg)

        logger.info(f"Loading {len(services_config)} services from {file_path}...")

        for config in services_config:
            register_service(config)

    except Exception as e:
        logger.error(f"Failed to load services from config {file_path}: {e}")
        # We don't raise here to allow other files to load if one fails


def _resolve_module(module_name: str) -> Optional[Any]:
    """
    Attempt to import a module, trying alternative paths if necessary.
    """
    try:
        # Try original module path first
        return importlib.import_module(module_name)
    except ImportError:
        # If it starts with 'backend.src', try without 'backend.' prefix
        if module_name.startswith('backend.src'):
            try:
                alt_module_name = module_name.replace('backend.', '', 1)
                module = importlib.import_module(alt_module_name)
                logger.debug(f"Loaded module using alternative path: {alt_module_name}")
                return module
            except ImportError as e:
                logger.error(f"Failed to import module {module_name} or {alt_module_name}: {e}")
        else:
            logger.error(f"Failed to import module {module_name}")
    
    return None


def register_service(config: Dict[str, Any]):
    """
    Register a single service based on configuration dictionary.
    Uses factory pattern for thread-safe instance creation.
    """
    try:
        # 验证 configuration using Pydantic
        try:
            service_config = ServiceConfig(**config)
        except ValidationError as e:
            logger.error(f"Invalid service configuration for {config.get('id')}: {e}")
            return
        
        # Skip disabled services
        if not service_config.enabled:
            logger.info(f"Skipping disabled service: {service_config.id}")
            return
        
        # Dynamic import
        module = _resolve_module(service_config.module)
        if not module:
            return
        
        try:
            service_class = getattr(module, service_config.class_name)
        except AttributeError as e:
            logger.error(f"Class {service_config.class_name} not found in module {service_config.module}: {e}")
            return
        
        # 创建 factory
        factory = ServiceFactory(service_config, service_class)
        
        # 转换 string type to Enum
        try:
            model_type = ModelType(service_config.type.lower())
        except ValueError:
            logger.warning(f"Invalid model type '{service_config.type}' for service {service_config.id}")
            return
            
        # Register factory
        ModelRegistry.register_factory(
            name=service_config.id,
            factory=factory,
            model_type=model_type,
            is_default=service_config.is_default
        )
        
    except Exception as e:
        logger.error(f"Failed to register service {config.get('id')}: {e}", exc_info=True)
