"""
Generation Helpers - 辅助函数模块

包含:
- resolve_service: 服务路由函数
- ensure_url: URL 处理函数
- _get_available_styles_from_config: 样式配置获取
""" 
import logging
import base64
import uuid
import os
import json
from functools import lru_cache
from typing import List, Optional

from src.services.provider.registry import ModelRegistry, ModelType
from src.services.storage_service import storage_manager
from src.config.settings import REDIS_ENABLED
from src.services.cache_service import get_cache_service
from src.utils.errors import ErrorCode, AppException
from src.services.user_api_key_service import user_api_key_service

logger = logging.getLogger(__name__)


def _is_test_env() -> bool:
    return (
        os.getenv("PYTEST_CURRENT_TEST") is not None
        or os.getenv("PIXEL_TEST_MODE") == "1"
        or os.getenv("NODE_ENV") == "test"
    )


def _ensure_model_registry_loaded() -> None:
    if ModelRegistry.list_models():
        return

    from src.utils.service_loader import load_services_from_config

    services_config_path = os.path.join(
        os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
        "config",
        "services",
    )
    load_services_from_config(services_config_path)


def _reload_model_registry() -> None:
    from src.utils.service_loader import load_services_from_config

    services_config_path = os.path.join(
        os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
        "config",
        "services",
    )
    load_services_from_config(services_config_path)


@lru_cache(maxsize=128)
def resolve_service(
    model: str,
    model_type: ModelType
):
    """
    根据复合 ID 路由到正确的服务
    
    路由逻辑：
    1. 验证 model 必须是 provider/model_key 格式
    2. 解析 provider 和 model_key
    3. 尝试用复合 ID 直接查找
    4. 在该 provider 下搜索匹配的 model_key
    5. 如果都找不到，抛出 HTTPException(404)
    
    Args:
        model: 复合 ID 格式 "provider/model_key"（如 'dashscope/qwen-image'）
        model_type: 模型类型（IMAGE, VIDEO, AUDIO 等）
    
    Returns:
        服务实例
    
    Raises:
        ValueError: 当 model 格式不正确时
        HTTPException: 当模型不存在时
    """
    _ensure_model_registry_loaded()

    # 1. 验证格式
    if not model or '/' not in model:
        raise ValueError(
            f"Model must be in format 'provider/model_key', got: '{model}'. "
            f"Example: 'dashscope/qwen-image'"
        )
    
    # 2. 解析 provider 和 model_key
    parts = model.split('/', 1)
    if len(parts) != 2:
        raise ValueError(
            f"Model format invalid: '{model}'. "
            f"Must have exactly one '/' separator."
        )
    
    provider, model_key = parts
    if not provider or not model_key:
        raise ValueError(
            f"Model format invalid: '{model}'. "
            f"Both provider and model_key must be non-empty."
        )
    
    logger.info(f"Resolving service: model='{model}', type={model_type.value}")
    
    # 3. 方式1: 直接用复合 ID 查找
    service = ModelRegistry.get(model)
    if service:
        logger.info(f"Found service by composite ID: '{model}'")
        return service
    
    # 4. 方式2: 在 provider 下搜索 model_key
    services = ModelRegistry.find_services(provider=provider, model_type=model_type)
    matching = [
        s for s in services 
        if s.get('model_key') == model_key or s.get('id') == model
    ]
    
    if matching:
        service = ModelRegistry.get(matching[0].get('id'))
        logger.info(
            f"Found service by provider+model_key: "
            f"provider='{provider}', model_key='{model_key}'"
        )
        return service

    # 4.5 可能 registry 被测试或运行时临时覆盖，尝试强制回填一次
    _reload_model_registry()

    service = ModelRegistry.get(model)
    if service:
        logger.info(f"Found service by composite ID after registry reload: '{model}'")
        return service

    services = ModelRegistry.find_services(provider=provider, model_type=model_type)
    matching = [
        s for s in services
        if s.get('model_key') == model_key or s.get('id') == model
    ]
    if matching:
        service = ModelRegistry.get(matching[0].get('id'))
        logger.info(
            f"Found service by provider+model_key after registry reload: "
            f"provider='{provider}', model_key='{model_key}'"
        )
        return service
    
    # 5. 未找到
    logger.error(f"Model '{model}' not found")
    raise AppException(
        message=f"Model '{model}' not found. Available models can be fetched from /api/v1/models",
        code=ErrorCode.NOT_FOUND,
        status_code=404
    )


def ensure_url(url_or_base64: str) -> str:
    """
    Ensure the input is a valid URL that external services can access.
    - If it's a relative path and using local storage, convert to Base64.
    - If it's a relative path and using OSS, upload to OSS.
    - If it's a Base64 string, return as is.
    - If it's a localhost URL, download and convert to Base64 or upload to OSS.
    - If it's already a public URL, sign it if needed.
    """
    if not url_or_base64:
        return url_or_base64
    
    # Check if it's a relative path (starts with /files/ or /uploads/)
    if url_or_base64.startswith('/files/') or url_or_base64.startswith('/uploads/'):
        try:
            from src.config.settings import DATA_DIR, UPLOAD_DIR, STORAGE_TYPE
            
            logger.info(f"[ensure_url] Detected relative path: {url_or_base64}")
            
            # Determine the file path
            if url_or_base64.startswith('/files/'):
                rel_path = url_or_base64[7:]  # Remove '/files/'
                file_path = os.path.join(DATA_DIR, rel_path)
            else:  # /uploads/
                rel_path = url_or_base64[9:]  # Remove '/uploads/'
                file_path = os.path.join(UPLOAD_DIR, rel_path)
            
            # Check if file exists
            if not os.path.exists(file_path):
                logger.error(f"[ensure_url] File not found: {file_path}")
                return url_or_base64
            
            # Read the file
            with open(file_path, 'rb') as f:
                file_data = f.read()
            
            # Determine extension and MIME type
            ext = os.path.splitext(file_path)[1].lstrip('.') or 'png'
            mime_type = f"image/{ext}"
            if ext == 'jpg':
                mime_type = "image/jpeg"
            
            # If using OSS, upload the file to OSS
            if STORAGE_TYPE == 'oss':
                filename = f"temp/{uuid.uuid4()}.{ext}"
                oss_url = storage_manager.save(filename, file_data)
                logger.info(f"[ensure_url] Uploaded to OSS: {oss_url}")
                return oss_url
            
             # If using local storage, convert to Base64
            else:
                base64_data = base64.b64encode(file_data).decode('utf-8')
                base64_url = f"data:{mime_type};base64,{base64_data}"
                logger.info(f"[ensure_url] Converted to Base64 (length: {len(base64_url)})")
                return base64_url
            
        except Exception as e:
            logger.error(f"[ensure_url] Failed to convert relative path: {e}")
            return url_or_base64
    
    # Check if it's a localhost URL — read from disk instead of HTTP to prevent SSRF
    if any(host in url_or_base64.lower() for host in ['localhost', '127.0.0.1', '0.0.0.0']):
        try:
            from src.config.settings import STORAGE_TYPE
            from urllib.parse import urlparse

            logger.info(f"[ensure_url] Detected localhost URL, reading from disk: {url_or_base64}")

            # Extract the path component (e.g. /files/xxx or /uploads/xxx)
            parsed = urlparse(url_or_base64)
            path = parsed.path

            # Map URL path to local filesystem
            if path.startswith('/files/'):
                rel_path = path[7:]
                file_path = os.path.join(DATA_DIR, rel_path)
            elif path.startswith('/uploads/'):
                rel_path = path[9:]
                file_path = os.path.join(UPLOAD_DIR, rel_path)
            else:
                logger.warning(f"[ensure_url] Unsupported localhost path: {path}")
                return url_or_base64

            if not os.path.exists(file_path):
                logger.error(f"[ensure_url] File not found: {file_path}")
                return url_or_base64

            with open(file_path, 'rb') as f:
                file_data = f.read()

            ext = os.path.splitext(file_path)[1].lstrip('.') or 'png'
            mime_type = f"image/{ext}"
            if ext == 'jpg':
                mime_type = "image/jpeg"

            if STORAGE_TYPE == 'oss':
                filename = f"temp/{uuid.uuid4()}.{ext}"
                saved_url = storage_manager.save(filename, file_data)
                logger.info(f"[ensure_url] Uploaded to OSS: {saved_url}")
                return saved_url
            else:
                base64_data = base64.b64encode(file_data).decode('utf-8')
                base64_url = f"data:{mime_type};base64,{base64_data}"
                logger.info(f"[ensure_url] Converted to Base64 (length: {len(base64_url)})")
                return base64_url

        except Exception as e:
            logger.error(f"[ensure_url] Failed to convert localhost URL: {e}")
            return url_or_base64
        
    # Check if Base64 - return as is (already in correct format)
    if url_or_base64.startswith("data:"):
        logger.info(f"[ensure_url] Already Base64 format, returning as is")
        return url_or_base64
    
    # If it's already a public URL, check if it belongs to our OSS bucket
    if url_or_base64.startswith(('http://', 'https://')):
        from src.config.settings import STORAGE_TYPE
        from src.utils.oss_utils import OSS_BUCKET
        
        # Only sign URLs from our own OSS bucket; external URLs should pass through as-is
        if STORAGE_TYPE == 'oss' and OSS_BUCKET and f"{OSS_BUCKET}." in url_or_base64:
            return storage_manager.sign_url(url_or_base64) or url_or_base64
        else:
            # External public URL (e.g. dashscope-result, other CDN), return as-is
            logger.info(f"[ensure_url] External public URL, returning as-is")
            return url_or_base64
                
    # Fallback: sign it if needed
    return storage_manager.sign_url(url_or_base64) or url_or_base64


async def get_available_styles_from_config() -> List[dict]:
    """ 获取 available style configurations from backend config file.
    
    Returns:
        List of style dicts with full information (id, name, desc, type, etc.)
        Example: [{'id': 'cyberpunk', 'name': '赛博朋克', 'desc': '高对比度霓虹灯光...', ...}, ...]
    """ 
    cache_key = "config:styles_full"
    
     # Cache first (5 minutes TTL)
    if REDIS_ENABLED:
        cache = get_cache_service()
        cached_styles = await cache.get(cache_key)
        if cached_styles is not None:
            return cached_styles
    
    # Get from config file
    src_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
    styles_path = os.path.join(src_dir, "config", "styles.json")
    
    styles_list = []
    if os.path.exists(styles_path):
        with open(styles_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
            styles_list = data.get('styles', [])
    
    # Fallback to common styles with descriptions if config doesn't exist
    if not styles_list:
        styles_list = [
            {"id": "cyberpunk", "name": "赛博朋克", "desc": "高对比度霓虹灯光，未来主义建筑，机械改造"},
            {"id": "ink", "name": "水墨风", "desc": "传统中国水墨渲染，黑白灰为主，意境深远"},
            {"id": "pixar", "name": "皮克斯风格", "desc": "3D卡通渲染，色彩鲜艳，光影柔和，表情夸张"},
            {"id": "anime", "name": "日漫", "desc": "典型日本动画风格，线条清晰，赛璐璐上色"},
            {"id": "chinese-anime", "name": "国漫风", "desc": "中国现代动画风格，融合传统与现代元素"},
            {"id": "realistic", "name": "写实", "desc": "电影级写实渲染，细节丰富，光照真实"},
            {"id": "hand-drawn", "name": "手绘", "desc": "传统手绘质感，笔触明显，艺术感强"},
            {"id": "watercolor", "name": "水彩画", "desc": "水彩晕染效果，色彩通透，艺术感强"},
            {"id": "oil-painting", "name": "油画", "desc": "厚涂油画质感，笔触丰富，光影层次感强"},
            {"id": "pixel-art", "name": "像素风", "desc": "复古8-bit/16-bit像素艺术，怀旧游戏风格"},
        ]
    
    # 缓存 for 5 minutes
    if REDIS_ENABLED:
        cache = get_cache_service()
        await cache.set(cache_key, styles_list, ttl=300)

    return styles_list


def check_user_api_key(user_id: str, provider: str) -> None:
    """
    检查系统是否配置了指定 Provider 的 API Key。

    Args:
        user_id: 用户ID（保留参数兼容性）
        provider: 服务商 ID (如 'dashscope', 'openai', 'kling' 等)

    Raises:
        AppException: 如果系统未配置该 Provider 的 API Key
    """
    if _is_test_env():
        return

    import os
    env_map = {
        "dashscope": "DASHSCOPE_API_KEY",
        "openai": "OPENAI_API_KEY",
        "kling": "KLING_ACCESS_KEY",
        "midjourney": "MIDJOURNEY_API_KEY",
        "modelscope": "MODELSCOPE_API_TOKEN",
        "volcengine": "VOLCENGINE_API_KEY",
        "minimax": "MINIMAX_API_KEY",
        "google": "GOOGLE_API_KEY",
    }
    env_var = env_map.get(provider)
    if env_var and os.getenv(env_var):
        return

    provider_names = {
        "dashscope": "阿里云 DashScope",
        "openai": "OpenAI",
        "kling": "可灵 AI",
        "midjourney": "Midjourney",
        "modelscope": "ModelScope",
        "volcengine": "火山引擎",
        "minimax": "MiniMax",
        "google": "Google",
    }
    provider_name = provider_names.get(provider, provider)

    raise AppException(
        message=f"未配置 {provider_name} 的 API Key",
        code=ErrorCode.INVALID_PARAMETER,
        status_code=400,
        details={
            "error": "API_KEY_NOT_CONFIGURED",
            "message": f"尚未配置 {provider_name} 的 API Key，无法提交生成任务。",
            "provider": provider,
            "provider_name": provider_name,
            "solution": "请联系管理员在 .env 中配置对应的 API Key。",
        }
    )
