package com.example.demo.service;

import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.config.DynamicApiConfig;
import com.example.demo.model.SystemSettings;

import com.fasterxml.jackson.databind.ObjectMapper;

import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import kong.unirest.UnirestException;

/**
 * 真实AI服务类
 * 调用外部AI API进行视频生成
 */
@Service
public class RealAIService {

    private static final Logger logger = LoggerFactory.getLogger(RealAIService.class);

    @Autowired
    private DynamicApiConfig dynamicApiConfig;

    @Autowired
    private SystemSettingsService systemSettingsService;

    private final ObjectMapper objectMapper;

    public RealAIService() {
        this.objectMapper = new ObjectMapper();
        // 配置ObjectMapper：保留null值（API可能期望null而不是省略字段）
        // objectMapper.setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include.ALWAYS);
        // 默认行为是包含null值，这是正确的
        
        // Unirest配置已移至 UnirestConfig 统一管理，避免重复配置警告
    }
    
    /**
     * 获取当前有效的API密钥（使用动态配置）
     */
    private String getEffectiveApiKey() {
        return dynamicApiConfig.getApiKey();
    }
    
    /**
     * 获取当前有效的API基础URL（使用动态配置）
     */
    private String getEffectiveApiBaseUrl() {
        return dynamicApiConfig.getApiBaseUrl();
    }
    
    /**
     * 获取当前有效的图片API密钥（使用动态配置）
     */
    private String getEffectiveImageApiKey() {
        return dynamicApiConfig.getImageApiKey();
    }
    
    /**
     * 获取当前有效的图片API基础URL（使用动态配置）
     */
    private String getEffectiveImageApiBaseUrl() {
        return dynamicApiConfig.getImageApiBaseUrl();
    }

    /**
     * 提交图生视频任务（多张图片，参考sora2实现）
     * 参考Comfly.py 6285-6292行：使用images数组
     */
    public Map<String, Object> submitStoryboardVideoTask(String prompt, List<String> images, 
                                                         String aspectRatio, String duration, 
                                                         boolean hdMode) {
        int maxRetries = 3;
        int retryCount = 0;
        long baseDelayMs = 5000; // 基础延迟5秒
        
        while (retryCount < maxRetries) {
            try {
                // 分镜视频：根据高清模式选择模型
                String modelName = selectStoryboardVideoModel(aspectRatio, duration, hdMode);
                
                // 验证图片格式（参考sora2实现，确保每张图片都有data URI前缀）
                List<String> validatedImages = validateImageFormat(images);
                
                // 使用 Sora2 端点（参考Comfly.py 6297行）
                String url = getEffectiveApiBaseUrl() + "/v2/videos/generations";
                
                // 使用 Sora2 API 的请求格式（参考Comfly.py 6285-6292行）
                Map<String, Object> requestMap = new HashMap<>();
                requestMap.put("prompt", prompt);
                requestMap.put("model", modelName);
                requestMap.put("images", validatedImages);  // 使用images数组（参考sora2实现）
                requestMap.put("size", convertAspectRatioToSize(aspectRatio, hdMode));
                requestMap.put("aspect_ratio", aspectRatio);
                requestMap.put("duration", duration);
                requestMap.put("hd", hdMode);
                
                String requestBody;
                try {
                    requestBody = objectMapper.writeValueAsString(requestMap);
                } catch (Exception e) {
                    logger.error("构建JSON请求体失败", e);
                    throw new RuntimeException("构建请求体失败: " + e.getMessage(), e);
                }
                
                // 记录请求体大小
                long requestBodySize = requestBody.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
                long requestBodySizeMB = requestBodySize / (1024 * 1024);
                long requestBodySizeKB = requestBodySize / 1024;
                
                if (retryCount > 0) {
                    logger.info("分镜视频请求重试 (第{}次): model={}", retryCount + 1, modelName);
                }
                
                HttpResponse<String> response = Unirest.post(url)
                    .header("Authorization", "Bearer " + getEffectiveApiKey())
                    .header("Content-Type", "application/json; charset=UTF-8")
                    .header("Accept", "application/json")
                    .header("Connection", "keep-alive")
                    .body(requestBody)
                    .asString();

                String responseBodyStr = response.getBody();

                if (response.getStatus() == 200 && responseBodyStr != null) {
                    String trimmedResponse = responseBodyStr.trim();
                    String lowerResponse = trimmedResponse.toLowerCase();
                    if (lowerResponse.startsWith("<!") || lowerResponse.startsWith("<html") || 
                        lowerResponse.contains("<!doctype") || (!trimmedResponse.startsWith("{") && !trimmedResponse.startsWith("["))) {
                        logger.error("API返回HTML页面而不是JSON，可能是认证失败或API端点错误");
                        throw new RuntimeException("API返回HTML页面，可能是认证失败。请检查API密钥和端点配置");
                    }
                    
                    try {
                        @SuppressWarnings("unchecked")
                        Map<String, Object> responseBody = objectMapper.readValue(responseBodyStr, Map.class);
                        
                        // 支持两种响应格式：{"task_id": "xxx"} 或 {"id": "xxx"}
                        String taskId = null;
                        if (responseBody.containsKey("task_id")) {
                            taskId = String.valueOf(responseBody.get("task_id"));
                        } else if (responseBody.containsKey("id")) {
                            taskId = String.valueOf(responseBody.get("id"));
                        }
                        
                        if (taskId != null && !taskId.isEmpty() && !"null".equals(taskId)) {
                            logger.info("分镜视频任务提交成功，task_id: {}", taskId);
                            Map<String, Object> result = new HashMap<>();
                            result.put("code", 200);
                            result.put("data", responseBody);
                            result.put("task_id", taskId);
                            return result;
                        } else {
                            // 处理错误响应
                            String errorMsg = responseBody.containsKey("message") ? 
                                responseBody.get("message").toString() : "未知错误";
                            logger.error("分镜视频任务提交失败: {}", errorMsg);
                            throw new RuntimeException("API返回错误: " + errorMsg);
                        }
                    } catch (com.fasterxml.jackson.core.JsonProcessingException e) {
                        logger.error("解析API响应JSON失败: {}", responseBodyStr, e);
                        throw new RuntimeException("解析API响应失败: " + e.getMessage());
                    }
                } else {
                    String errorMsg = String.format("API请求失败，HTTP状态: %d, 响应: %s", 
                                                   response.getStatus(), responseBodyStr);
                    logger.error(errorMsg);
                    throw new RuntimeException(errorMsg);
                }
                
            } catch (Exception e) {
                retryCount++;
                if (retryCount >= maxRetries) {
                    logger.error("分镜视频任务提交失败，已重试{}次: {}", maxRetries, e.getMessage(), e);
                    throw new RuntimeException("分镜视频任务提交失败: " + e.getMessage(), e);
                }
                
                long delayMs = baseDelayMs * retryCount;
                logger.warn("分镜视频任务提交失败，{}秒后重试 (第{}次，共{}次): {}", 
                           delayMs / 1000, retryCount, maxRetries, e.getMessage());
                try {
                    Thread.sleep(delayMs);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("重试等待被中断", ie);
                }
            }
        }
        
        throw new RuntimeException("分镜视频任务提交失败，已重试" + maxRetries + "次");
    }

    /**
     * 提交图生视频任务（带重试机制）
     */
    public Map<String, Object> submitImageToVideoTask(String prompt, String imageBase64, 
                                                     String aspectRatio, String duration, 
                                                     boolean hdMode) {
        int maxRetries = 3;
        int retryCount = 0;
        long baseDelayMs = 5000; // 基础延迟5秒（给服务器更多恢复时间）
        int retryAttempt = 0; // 重试次数计数器
        
        while (retryCount < maxRetries) {
        try {
            // 根据参数选择可用的模型
            String modelName = selectAvailableImageToVideoModel(aspectRatio, duration, hdMode);
            
                // 处理图片数据：如果是 URL，先下载转换为 base64
                String processedImageBase64 = imageBase64;
                if (imageBase64.startsWith("http://") || imageBase64.startsWith("https://")) {
                    // 是 URL，需要下载图片
                    logger.info("检测到图片URL，开始下载: {}", imageBase64.substring(0, Math.min(100, imageBase64.length())));
                    try {
                        java.net.URL imageUrl = new java.net.URL(imageBase64);
                        java.net.HttpURLConnection conn = (java.net.HttpURLConnection) imageUrl.openConnection();
                        conn.setConnectTimeout(30000);
                        conn.setReadTimeout(60000);
                        try (java.io.InputStream is = conn.getInputStream();
                             java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream()) {
                            byte[] buffer = new byte[8192];
                            int bytesRead;
                            while ((bytesRead = is.read(buffer)) != -1) {
                                baos.write(buffer, 0, bytesRead);
                            }
                            byte[] imageData = baos.toByteArray();
                            // 添加 data URI 前缀
                            String mimeType = conn.getContentType();
                            if (mimeType == null || !mimeType.startsWith("image/")) {
                                mimeType = "image/png"; // 默认使用 PNG
                            }
                            processedImageBase64 = "data:" + mimeType + ";base64," + Base64.getEncoder().encodeToString(imageData);
                            logger.info("图片下载成功，大小: {} KB", imageData.length / 1024);
                        }
                    } catch (Exception e) {
                        logger.error("下载图片失败: {}", e.getMessage());
                        throw new RuntimeException("下载参考图片失败: " + e.getMessage());
                    }
                }
                
                // 验证base64数据格式（提取纯Base64数据用于验证）
                String base64DataForValidation = processedImageBase64;
            if (processedImageBase64.contains(",")) {
                    base64DataForValidation = processedImageBase64.substring(processedImageBase64.indexOf(",") + 1);
            }
            try {
                    Base64.getDecoder().decode(base64DataForValidation);
            } catch (IllegalArgumentException e) {
                logger.error("Base64数据格式错误: {}", e.getMessage());
                throw new RuntimeException("图片数据格式错误");
            }
            
            // 根据分辨率选择size参数
            String size = convertAspectRatioToSize(aspectRatio, hdMode);
            
                // 使用 Sora2 端点（与文生视频使用相同的端点，参考 Comfly.py 6297 行）
                String url = getEffectiveApiBaseUrl() + "/v2/videos/generations";
                
                // 使用 Sora2 API 的请求格式（参考 Comfly.py 6285-6292行）
                // 图生视频使用 images 数组（即使只有一张图片）
                // 验证并规范化图片格式（参考sora2实现）
                List<String> imagesList = new java.util.ArrayList<>();
                imagesList.add(processedImageBase64);
                imagesList = validateImageFormat(imagesList);
                
                Map<String, Object> requestMap = new HashMap<>();
                requestMap.put("prompt", prompt);
                requestMap.put("model", modelName);
                requestMap.put("images", imagesList);  // 使用 images 数组，不是单个 image
                requestMap.put("size", size);
                requestMap.put("aspect_ratio", aspectRatio);
                requestMap.put("duration", duration);
                requestMap.put("hd", hdMode);
                
                String requestBody;
                try {
                    requestBody = objectMapper.writeValueAsString(requestMap);
                } catch (Exception e) {
                    logger.error("构建JSON请求体失败", e);
                    throw new RuntimeException("构建请求体失败: " + e.getMessage(), e);
                }
                
                // 记录请求体大小（Base64编码后的图片可能很大）
                long requestBodySize = requestBody.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
                long requestBodySizeMB = requestBodySize / (1024 * 1024);
                long requestBodySizeKB = requestBodySize / 1024;
                
                if (retryCount > 0) {
                    logger.info("图生视频请求重试 (第{}次): model={}", retryCount + 1, modelName);
                }
                
                // 使用流式传输，避免一次性加载整个请求体到内存
                // 添加额外的请求头以支持大请求体
            HttpResponse<String> response = Unirest.post(url)
                .header("Authorization", "Bearer " + getEffectiveApiKey())
                    .header("Content-Type", "application/json; charset=UTF-8")
                    .header("Accept", "application/json")
                    .header("Connection", "keep-alive")
                .body(requestBody)
                .asString();

            // 添加响应调试日志
            logger.info("API响应状态: {}", response.getStatus());
            String responseBodyStr = response.getBody();
            logger.info("API响应内容（前500字符）: {}", responseBodyStr != null && responseBodyStr.length() > 500 ? 
                       responseBodyStr.substring(0, 500) : responseBodyStr);

            if (response.getStatus() == 200 && responseBodyStr != null) {
                // 检查响应是否为HTML（可能是认证失败或API端点错误）
                String trimmedResponse = responseBodyStr.trim();
                String lowerResponse = trimmedResponse.toLowerCase();
                if (lowerResponse.startsWith("<!") || lowerResponse.startsWith("<html") || 
                    lowerResponse.contains("<!doctype") || (!trimmedResponse.startsWith("{") && !trimmedResponse.startsWith("["))) {
                    logger.error("API返回HTML页面而不是JSON，可能是认证失败或API端点错误");
                    logger.error("响应前100字符: {}", trimmedResponse.length() > 100 ? trimmedResponse.substring(0, 100) : trimmedResponse);
                    logger.error("请检查：1) API密钥是否正确 2) API端点URL是否正确 3) API服务是否正常运行");
                    throw new RuntimeException("API返回HTML页面，可能是认证失败。请检查API密钥和端点配置");
                }
                
                try {
                    @SuppressWarnings("unchecked")
                    Map<String, Object> responseBody = objectMapper.readValue(responseBodyStr, Map.class);
                    
                    // 支持两种响应格式：{"task_id": "xxx"} 或 {"id": "xxx"}
                    String taskId = null;
                    if (responseBody.containsKey("task_id")) {
                        taskId = String.valueOf(responseBody.get("task_id"));
                    } else if (responseBody.containsKey("id")) {
                        taskId = String.valueOf(responseBody.get("id"));
                    }
                    
                    if (taskId != null && !taskId.isEmpty() && !"null".equals(taskId)) {
                        logger.info("图生视频任务提交成功，task_id: {}", taskId);
                        Map<String, Object> result = new HashMap<>();
                        result.put("code", 200);
                        result.put("data", responseBody);
                        result.put("task_id", taskId);
                        return result;
                    } else {
                        // 处理错误响应
                        logger.error("图生视频任务提交失败，响应中缺少task_id或id: {}", responseBody);
                        String errorMsg = "未知错误";
                        if (responseBody.get("message") != null) {
                            errorMsg = responseBody.get("message").toString();
                        }
                        throw new RuntimeException("任务提交失败: " + errorMsg);
                    }
                } catch (com.fasterxml.jackson.core.JsonParseException e) {
                    logger.error("解析API响应为JSON失败，响应内容可能是HTML或其他格式", e);
                    logger.error("响应内容前200字符: {}", responseBodyStr.length() > 200 ? 
                               responseBodyStr.substring(0, 200) : responseBodyStr);
                    throw new RuntimeException("API返回非JSON响应，可能是认证失败。请检查API密钥和端点配置");
                }
            } else {
                logger.error("图生视频任务提交失败，HTTP状态: {}", response.getStatus());
                throw new RuntimeException("任务提交失败，HTTP状态: " + response.getStatus());
            }

        } catch (UnirestException e) {
                retryCount++;
                retryAttempt++;
                Throwable cause = e.getCause();
                String errorMessage = e.getMessage();
                
                // 详细记录错误信息用于诊断
                logger.warn("UnirestException详情: message={}, cause={}, causeClass={}", 
                           errorMessage, 
                           cause != null ? cause.getMessage() : "null",
                           cause != null ? cause.getClass().getName() : "null");
                
                // 判断是否为可重试的错误
                boolean isRetryable = false;
                if (cause != null) {
                    String causeMessage = cause.getMessage();
                    
                    // Connection reset, connection refused, timeout 等可重试错误
                    if (cause instanceof java.net.SocketException) {
                        isRetryable = true;
                        logger.warn("网络连接错误 (可重试): {}", causeMessage);
                        // 如果是 Connection reset，可能是服务器端问题或请求体过大
                        if (causeMessage != null && causeMessage.contains("Connection reset")) {
                            logger.warn("Connection reset 可能原因: 1) 服务器端限制请求体大小 2) 服务器端超时 3) 网络不稳定");
                        }
                    } else if (cause instanceof java.net.ConnectException) {
                        isRetryable = true;
                        logger.warn("连接被拒绝 (可重试): {}", causeMessage);
                    } else if (cause instanceof java.net.SocketTimeoutException) {
                        isRetryable = true;
                        logger.warn("连接超时 (可重试): {}", causeMessage);
                    } else if (errorMessage != null && (
                        errorMessage.contains("Connection reset") ||
                        errorMessage.contains("Connection refused") ||
                        errorMessage.contains("timeout") ||
                        errorMessage.contains("SocketException"))) {
                        isRetryable = true;
                        logger.warn("网络错误 (可重试): {}", errorMessage);
                    }
                }
                
                if (isRetryable && retryCount < maxRetries) {
                    // 指数退避：第1次重试等待5秒，第2次等待10秒，第3次等待20秒
                    long delayMs = baseDelayMs * (1L << (retryAttempt - 1));
                    logger.warn("提交图生视频任务失败 (第{}次尝试，共{}次)，{} 秒后重试... 错误: {}", 
                               retryCount, maxRetries, delayMs / 1000, errorMessage);
                    logger.warn("提示: 如果请求体较大，可能是网络传输时被服务器重置连接，重试时将等待更长时间");
                    try {
                        Thread.sleep(delayMs);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new RuntimeException("重试等待被中断", ie);
                    }
                    continue; // 重试
                } else {
                    logger.error("提交图生视频任务异常 (已重试{}次): {}", retryCount, errorMessage, e);
                    logger.error("建议: 1) 检查网络连接 2) 检查请求体大小是否过大 3) 联系API服务提供商检查服务器状态");
                    throw new RuntimeException("提交任务失败: " + errorMessage, e);
                }
        } catch (Exception e) {
                // 其他不可重试的错误直接抛出
            logger.error("提交图生视频任务异常", e);
                throw new RuntimeException("提交任务失败: " + e.getMessage(), e);
        }
        }
        
        // 理论上不会到达这里，但为了编译通过
        throw new RuntimeException("提交图生视频任务失败，已重试" + maxRetries + "次");
    }

    /**
     * 提交文生视频任务
     */
    public Map<String, Object> submitTextToVideoTask(String prompt, String aspectRatio, 
                                                    String duration, boolean hdMode) {
        try {
            // 根据参考代码，文生视频支持5秒和10秒，duration直接传入，不转换
            // 根据参数选择可用的模型
            String modelName = selectAvailableTextToVideoModel(aspectRatio, duration, hdMode);

            // 提交文生视频任务（参考Comfly_sora2实现）
            // 使用 /v2/videos/generations 端点，JSON格式

            String url = getEffectiveApiBaseUrl() + "/v2/videos/generations";
            
            // 构建请求体（参考Comfly项目Comfly_sora2类的格式）
            Map<String, Object> requestBodyMap = new HashMap<>();
            requestBodyMap.put("prompt", prompt);
            requestBodyMap.put("model", modelName);
            requestBodyMap.put("aspect_ratio", aspectRatio);
            requestBodyMap.put("duration", duration); // duration是字符串类型
            requestBodyMap.put("hd", hdMode);
            
            String requestBody = objectMapper.writeValueAsString(requestBodyMap);
            
            logger.info("提交文生视频任务 - URL: {}", url);
            logger.info("提交文生视频任务 - 请求体: {}", requestBody);
            
            // 使用 JSON 格式（参考Comfly_sora2实现）
            HttpResponse<String> response = Unirest.post(url)
                .header("Authorization", "Bearer " + getEffectiveApiKey())
                .header("Content-Type", "application/json")
                .body(requestBody)
                .asString();

            // 添加响应调试日志
            logger.info("API响应状态: {}", response.getStatus());
            String responseBodyStr = response.getBody();
            logger.info("API响应内容（前500字符）: {}", responseBodyStr != null && responseBodyStr.length() > 500 ? 
                       responseBodyStr.substring(0, 500) : responseBodyStr);

            if (response.getStatus() == 200 && responseBodyStr != null) {
                // 检查响应是否为HTML（可能是认证失败或API端点错误）
                String trimmedResponse = responseBodyStr.trim();
                String lowerResponse = trimmedResponse.toLowerCase();
                if (lowerResponse.startsWith("<!") || lowerResponse.startsWith("<html") || 
                    lowerResponse.contains("<!doctype") || (!trimmedResponse.startsWith("{") && !trimmedResponse.startsWith("["))) {
                    logger.error("API返回HTML页面而不是JSON，可能是认证失败或API端点错误");
                    logger.error("响应前100字符: {}", trimmedResponse.length() > 100 ? trimmedResponse.substring(0, 100) : trimmedResponse);
                    logger.error("请检查：1) API密钥是否正确 2) API端点URL是否正确 3) API服务是否正常运行");
                    throw new RuntimeException("API返回HTML页面，可能是认证失败。请检查API密钥和端点配置");
                }
                
                try {
                    @SuppressWarnings("unchecked")
                    Map<String, Object> responseBody = objectMapper.readValue(responseBodyStr, Map.class);
                    
                    // 支持两种响应格式：{"task_id": "xxx"} 或 {"id": "xxx"}
                    String taskId = null;
                    if (responseBody.containsKey("task_id")) {
                        taskId = String.valueOf(responseBody.get("task_id"));
                    } else if (responseBody.containsKey("id")) {
                        taskId = String.valueOf(responseBody.get("id"));
                    }
                    
                    if (taskId != null && !taskId.isEmpty() && !"null".equals(taskId)) {
                        logger.info("文生视频任务提交成功，task_id: {}", taskId);
                        // 转换为统一的响应格式
                        Map<String, Object> result = new HashMap<>();
                        result.put("code", 200);
                        result.put("data", responseBody);
                        result.put("task_id", taskId);
                        return result;
                    } else {
                        logger.error("文生视频任务提交失败，响应中缺少task_id或id: {}", responseBody);
                        throw new RuntimeException("任务提交失败: 响应格式不正确");
                    }
                } catch (com.fasterxml.jackson.core.JsonParseException e) {
                    logger.error("解析API响应为JSON失败，响应内容可能是HTML或其他格式", e);
                    logger.error("响应内容前200字符: {}", responseBodyStr.length() > 200 ? 
                               responseBodyStr.substring(0, 200) : responseBodyStr);
                    throw new RuntimeException("API返回非JSON响应，可能是认证失败。请检查API密钥和端点配置");
                }
            } else {
                logger.error("文生视频任务提交失败，HTTP状态: {}", response.getStatus());
                logger.error("请求URL: {}", url);
                logger.error("请求体: {}", requestBody);
                logger.error("响应体: {}", responseBodyStr);
                logger.error("可能的原因：");
                logger.error("1. API服务内部错误（500错误）");
                logger.error("2. 请求参数格式不正确");
                logger.error("3. API密钥权限不足");
                logger.error("4. API服务暂时不可用");
                throw new RuntimeException("任务提交失败，HTTP状态: " + response.getStatus() + ", 响应: " + responseBodyStr);
            }

        } catch (UnirestException e) {
            logger.error("提交文生视频任务异常", e);
            throw new RuntimeException("提交任务失败: " + e.getMessage());
        } catch (Exception e) {
            logger.error("提交文生视频任务异常", e);
            throw new RuntimeException("提交任务失败: " + e.getMessage());
        }
    }

    /**
     * 查询任务状态
     * 参考Comfly项目，使用 /v2/videos/generations/{task_id} 端点
     */
    public Map<String, Object> getTaskStatus(String taskId) {
        try {
            String url = getEffectiveApiBaseUrl() + "/v2/videos/generations/" + taskId;
            HttpResponse<String> response = Unirest.get(url)
                .header("Authorization", "Bearer " + getEffectiveApiKey())
                .header("Content-Type", "application/json")
                .asString();

            if (response.getStatus() == 200 && response.getBody() != null) {
                String responseBodyStr = response.getBody();
                
                @SuppressWarnings("unchecked")
                Map<String, Object> responseBody = objectMapper.readValue(responseBodyStr, Map.class);
                
                // 参考Comfly项目，响应格式为 {"status": "SUCCESS", "data": {"output": "video_url"}, ...}
                // 转换为统一的响应格式
                Map<String, Object> result = new HashMap<>();
                result.put("code", 200);
                result.put("data", responseBody);
                return result;
            } else {
                logger.error("查询任务状态失败，HTTP状态: {}", response.getStatus());
                throw new RuntimeException("查询任务状态失败，HTTP状态: " + response.getStatus());
            }

        } catch (UnirestException e) {
            logger.error("查询任务状态异常: taskId={}", taskId, e);
            throw new RuntimeException("查询任务状态失败: " + e.getMessage());
        } catch (Exception e) {
            logger.error("查询任务状态异常: taskId={}", taskId, e);
            throw new RuntimeException("查询任务状态失败: " + e.getMessage());
        }
    }

    /**
     * 根据参数选择可用的图生视频模型
     */
    private String selectAvailableImageToVideoModel(String aspectRatio, String duration, boolean hdMode) {
        try {
            // 首先尝试获取可用模型列表
            Map<String, Object> modelsResponse = getAvailableModels();
            if (modelsResponse != null && modelsResponse.get("data") instanceof List) {
                @SuppressWarnings("unchecked")
                List<Map<String, Object>> taskTypes = (List<Map<String, Object>>) modelsResponse.get("data");
                
                // 查找图生视频任务类型
                for (@SuppressWarnings("unchecked") Map<String, Object> taskType : taskTypes) {
                    if ("image_to_video".equals(taskType.get("taskType"))) {
                        @SuppressWarnings("unchecked")
                        List<Map<String, Object>> models = (List<Map<String, Object>>) taskType.get("models");
                        
                        // 根据参数匹配模型
                        for (Map<String, Object> model : models) {
                            Map<String, Object> config = (Map<String, Object>) model.get("extendedConfig");
                            if (config != null) {
                                String modelAspectRatio = (String) config.get("aspectRatio");
                                String modelDuration = (String) config.get("duration");
                                String modelSize = (String) config.get("size");
                                Boolean isEnabled = (Boolean) model.get("isEnabled");
                                
                                // 检查是否匹配参数
                                if (isEnabled != null && isEnabled && 
                                    aspectRatio.equals(modelAspectRatio) && 
                                    duration.equals(modelDuration) &&
                                    (hdMode ? "large".equals(modelSize) : "small".equals(modelSize))) {
                                    
                                    String modelName = (String) model.get("modelName");
                                    logger.info("选择图生视频模型: {} (aspectRatio: {}, duration: {}, size: {})", 
                                               modelName, modelAspectRatio, modelDuration, modelSize);
                                    return modelName;
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.warn("获取可用图生视频模型失败，使用默认模型选择逻辑", e);
        }
        
        // 如果获取模型列表失败，使用默认逻辑
        return selectImageToVideoModel(aspectRatio, duration, hdMode);
    }

    /**
     * 根据参数选择图生视频模型（默认逻辑）
     * sora-2 支持: 16:9, 9:16, 10s, 15s
     * sora-2-pro 支持: 16:9, 9:16, 10s, 15s, 25s, HD
     */
    private String selectImageToVideoModel(String aspectRatio, String duration, boolean hdMode) {
        // 25s 时长必须使用 sora-2-pro
        if ("25".equals(duration)) {
            return "sora-2-pro";
        }
        // 其他情况使用 sora-2
        return "sora-2";
    }

    /**
     * 获取可用的模型列表
     * 注意：Comfly项目可能不需要这个端点，直接使用默认模型选择逻辑
     */
    public Map<String, Object> getAvailableModels() {
        // 暂时不调用模型列表API，直接返回null让调用方使用默认逻辑
        // 因为Comfly项目直接使用sora-2或sora-2-pro，不需要查询模型列表
        return null;
    }

    /**
     * 根据参数选择可用的文生视频模型
     */
    private String selectAvailableTextToVideoModel(String aspectRatio, String duration, boolean hdMode) {
        try {
            // 首先尝试获取可用模型列表
            Map<String, Object> modelsResponse = getAvailableModels();
            if (modelsResponse != null && modelsResponse.get("data") instanceof List) {
                @SuppressWarnings("unchecked")
                List<Map<String, Object>> taskTypes = (List<Map<String, Object>>) modelsResponse.get("data");
                
                // 查找文生视频任务类型
                for (@SuppressWarnings("unchecked") Map<String, Object> taskType : taskTypes) {
                    if ("text_to_video".equals(taskType.get("taskType"))) {
                        @SuppressWarnings("unchecked")
                        List<Map<String, Object>> models = (List<Map<String, Object>>) taskType.get("models");
                        
                        // 根据参数匹配模型
                        for (Map<String, Object> model : models) {
                            Map<String, Object> config = (Map<String, Object>) model.get("extendedConfig");
                            if (config != null) {
                                String modelAspectRatio = (String) config.get("aspectRatio");
                                String modelDuration = (String) config.get("duration");
                                String modelSize = (String) config.get("size");
                                Boolean isEnabled = (Boolean) model.get("isEnabled");
                                
                                // 检查是否匹配参数
                                if (isEnabled != null && isEnabled && 
                                    aspectRatio.equals(modelAspectRatio) && 
                                    duration.equals(modelDuration) &&
                                    (hdMode ? "large".equals(modelSize) : "small".equals(modelSize))) {
                                    
                                    String modelName = (String) model.get("modelName");
                                    logger.info("选择模型: {} (aspectRatio: {}, duration: {}, size: {})", 
                                               modelName, modelAspectRatio, modelDuration, modelSize);
                                    return modelName;
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.warn("获取可用模型失败，使用默认模型选择逻辑", e);
        }
        
        // 如果获取模型列表失败，使用默认逻辑
        return selectTextToVideoModel(aspectRatio, duration, hdMode);
    }

    /**
     * 根据参数选择文生视频模型（默认逻辑）
     * sora-2 支持: 16:9, 9:16, 10s, 15s
     * sora-2-pro 支持: 16:9, 9:16, 10s, 15s, 25s, HD
     */
    private String selectTextToVideoModel(String aspectRatio, String duration, boolean hdMode) {
        // 25s 时长必须使用 sora-2-pro
        if ("25".equals(duration)) {
            return "sora-2-pro";
        }
        // 其他情况使用 sora-2
        return "sora-2";
    }

    /**
     * 根据参数选择分镜视频模型
     * sora-2 支持: 16:9, 9:16, 10s, 15s
     * sora-2-pro 支持: 16:9, 9:16, 10s, 15s, 25s, HD
     */
    private String selectStoryboardVideoModel(String aspectRatio, String duration, boolean hdMode) {
        // 25s 时长或高清模式必须使用 sora-2-pro
        if ("25".equals(duration) || hdMode) {
            return "sora-2-pro";
        }
        return "sora-2";
    }

    /**
     * 验证并规范化图片格式（参考sora2实现）
     * 确保所有图片都是Base64格式，带data URI前缀
     * 支持PNG和JPEG格式（JPEG用于压缩后的图片）
     * 参考Comfly.py 6236行：data:image/png;base64,{base64_str} 或 data:image/jpeg;base64,{base64_str}
     */
    private List<String> validateImageFormat(List<String> images) {
        List<String> validatedImages = new java.util.ArrayList<>();
        for (String img : images) {
            if (img == null || img.isEmpty()) {
                continue;
            }
            
            String validatedImg = img;
            // 确保有data URI前缀（参考Comfly.py 6236行）
            if (!img.startsWith("data:")) {
                // 如果没有前缀，添加前缀
                if (img.contains(",")) {
                    // 如果已经有逗号，提取Base64数据并添加前缀
                    String pureBase64Data = img.substring(img.indexOf(",") + 1);
                    // 默认使用PNG格式（如果无法确定格式）
                    validatedImg = "data:image/png;base64," + pureBase64Data;
                } else {
                    // 假设是纯Base64数据
                    validatedImg = "data:image/png;base64," + img;
                }
            } else {
                // 如果已经有data URI前缀，检查格式
                // 支持 data:image/png;base64, 和 data:image/jpeg;base64,
                if (!img.startsWith("data:image/png;base64,") && 
                    !img.startsWith("data:image/jpeg;base64,") &&
                    !img.startsWith("data:image/jpg;base64,")) {
                    // 如果不是标准格式，尝试修复
                    if (img.contains(",")) {
                        String pureBase64Data = img.substring(img.indexOf(",") + 1);
                        // 保持原有格式或默认使用PNG
                        if (img.contains("jpeg") || img.contains("jpg")) {
                            validatedImg = "data:image/jpeg;base64," + pureBase64Data;
                        } else {
                            validatedImg = "data:image/png;base64," + pureBase64Data;
                        }
                    }
                }
            }
            validatedImages.add(validatedImg);
        }
        
        return validatedImages;
    }

    /**
     * 将图片文件转换为Base64（带压缩，避免请求体过大）
     * 参考分镜视频的压缩策略：最大1024px，JPEG质量85%
     */
    public String convertImageToBase64(byte[] imageBytes, String contentType) {
        try {
            // 先读取原始图片
            java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(imageBytes);
            java.awt.image.BufferedImage originalImage = javax.imageio.ImageIO.read(bais);
            
            if (originalImage == null) {
                logger.warn("无法读取图片，直接转Base64");
                String base64 = java.util.Base64.getEncoder().encodeToString(imageBytes);
                return "data:" + contentType + ";base64," + base64;
            }
            
            int originalWidth = originalImage.getWidth();
            int originalHeight = originalImage.getHeight();
            int originalSize = imageBytes.length;
            
            logger.info("原始图片: {}x{}, 大小: {} KB", 
                originalWidth, originalHeight, originalSize / 1024);
            
            // 压缩图片到最大1024px（参考分镜视频实现）
            int maxSize = 1024;
            
            // 检查是否需要压缩（与分镜视频逻辑一致）
            if (originalWidth <= maxSize && originalHeight <= maxSize && originalSize < 500 * 1024) {
                // 图片已经足够小，不需要压缩
                logger.info("图片尺寸和大小已满足要求，跳过压缩");
                String base64 = java.util.Base64.getEncoder().encodeToString(imageBytes);
                return "data:" + contentType + ";base64," + base64;
            }
            
            java.awt.image.BufferedImage compressedImage = originalImage;
            
            if (originalWidth > maxSize || originalHeight > maxSize) {
                // 计算缩放比例
                double scale = Math.min((double) maxSize / originalWidth, (double) maxSize / originalHeight);
                int newWidth = (int) (originalWidth * scale);
                int newHeight = (int) (originalHeight * scale);
                
                // 创建缩放后的图片
                compressedImage = new java.awt.image.BufferedImage(newWidth, newHeight, java.awt.image.BufferedImage.TYPE_INT_RGB);
                java.awt.Graphics2D g = compressedImage.createGraphics();
                
                // 设置高质量缩放
                g.setRenderingHint(java.awt.RenderingHints.KEY_INTERPOLATION, java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                g.setRenderingHint(java.awt.RenderingHints.KEY_RENDERING, java.awt.RenderingHints.VALUE_RENDER_QUALITY);
                g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
                
                g.drawImage(originalImage, 0, 0, newWidth, newHeight, null);
                g.dispose();
                
                logger.info("图片已缩放: {}x{} -> {}x{}", originalWidth, originalHeight, newWidth, newHeight);
            }
            
            // 转换为JPEG格式的Base64（质量85%）
            java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
            javax.imageio.ImageWriter writer = javax.imageio.ImageIO.getImageWritersByFormatName("jpg").next();
            javax.imageio.ImageWriteParam param = writer.getDefaultWriteParam();
            if (param.canWriteCompressed()) {
                param.setCompressionMode(javax.imageio.ImageWriteParam.MODE_EXPLICIT);
                param.setCompressionQuality(0.85f);
            }
            javax.imageio.IIOImage iioImage = new javax.imageio.IIOImage(compressedImage, null, null);
            writer.setOutput(javax.imageio.ImageIO.createImageOutputStream(baos));
            writer.write(null, iioImage, param);
            writer.dispose();
            
            byte[] compressedBytes = baos.toByteArray();
            double compressionRatio = (1.0 - (double) compressedBytes.length / originalSize) * 100;
            
            logger.info("图片压缩完成: {} KB -> {} KB (压缩率: {}%)", 
                originalSize / 1024, compressedBytes.length / 1024, 
                String.format("%.1f", compressionRatio));
            
            String base64 = java.util.Base64.getEncoder().encodeToString(compressedBytes);
            return "data:image/jpeg;base64," + base64;
            
        } catch (Exception e) {
            logger.error("图片转Base64失败", e);
            throw new RuntimeException("图片转换失败: " + e.getMessage());
        }
    }

    /**
     * 提交文生图任务（分镜视频使用）
     * 调用Comfly API的文生图接口，参考Comfly项目的Comfly_qwen_image节点实现
     * 支持分镜图生成，可以生成多张图片用于拼接成分镜图网格
     * 
     * @param prompt 提示词
     * @param aspectRatio 宽高比
     * @param numImages 生成图片数量，默认6张用于分镜图
     * @return API响应，包含多张图片的URL
     */
    public Map<String, Object> submitTextToImageTask(String prompt, String aspectRatio, int numImages) {
        return submitTextToImageTask(prompt, aspectRatio, numImages, false);
    }

    /**
     * 提交文生图任务（使用banana模型）
     * 参考Comfly项目的Comfly_nano_banana_edit节点实现
     */
    public Map<String, Object> submitTextToImageTask(String prompt, String aspectRatio, int numImages, boolean hdMode) {
        return submitTextToImageTask(prompt, aspectRatio, numImages, hdMode, "nano-banana-2");
    }

    /**
     * 提交文生图任务（使用指定的模型）
     * @param imageModel 图像生成模型：nano-banana 或 nano-banana-2
     */
    public Map<String, Object> submitTextToImageTask(String prompt, String aspectRatio, int numImages, boolean hdMode, String imageModel) {
        try {
            logger.info("提交文生图任务: prompt={}, aspectRatio={}, hdMode={}, imageModel={}", 
                        prompt, aspectRatio, hdMode, imageModel);
            
            // 获取系统引导词并拼接到用户提示词
            String finalPrompt = prompt;
            try {
                SystemSettings settings = systemSettingsService.getOrCreate();
                String systemPrompt = settings.getStoryboardSystemPrompt();
                if (systemPrompt != null && !systemPrompt.trim().isEmpty()) {
                    finalPrompt = systemPrompt.trim() + ", " + prompt;
                    logger.info("已添加系统引导词，最终提示词长度: {}", finalPrompt.length());
                }
            } catch (Exception e) {
                logger.warn("获取系统引导词失败，使用原始提示词: {}", e.getMessage());
            }
            
            // 注意：banana模型一次只生成1张图片，numImages参数用于兼容性，实际请求中不使用
            // 参考Comfly_nano_banana_edit节点：每次调用只生成1张图片
            
            // 直接使用模型对应的API端点，不使用系统配置的基础URL
            String url = "https://ai.comfly.chat" + "/v1/images/generations";
            logger.info("【调试】文生图API端点: {}", url);
            
            // 强制使用 nano-banana-2 模型
            String model = "nano-banana-2";
            logger.info("【调试】强制使用的模型参数: {}", model);
            
            // 构建请求体，参考Comfly_nano_banana_edit节点的参数设置
            // 注意：banana模型不需要n参数，每次只生成1张图片
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("prompt", finalPrompt);
            requestBody.put("model", model);
            requestBody.put("aspect_ratio", aspectRatio);  // 直接使用aspect_ratio，不需要转换为size
            requestBody.put("response_format", "url");  // 可选：url 或 b64_json
            
            String requestBodyJson = objectMapper.writeValueAsString(requestBody);
            logger.info("【调试】文生图请求体: {}", requestBodyJson);
            
            HttpResponse<String> response = Unirest.post(url)
                .header("Authorization", "Bearer " + getEffectiveImageApiKey())
                .header("Content-Type", "application/json")
                .body(requestBodyJson)
                .asString();
            logger.info("文生图API响应状态: {}", response.getStatus());
            String responseBodyStr = response.getBody();
            logger.info("文生图API响应内容（前500字符）: {}", responseBodyStr != null && responseBodyStr.length() > 500 ? 
                       responseBodyStr.substring(0, 500) : responseBodyStr);

            if (response.getStatus() == 200 && responseBodyStr != null) {
                // 检查响应是否为HTML（可能是认证失败或API端点错误）
                String trimmedResponse = responseBodyStr.trim();
                String lowerResponse = trimmedResponse.toLowerCase();
                if (lowerResponse.startsWith("<!") || lowerResponse.startsWith("<html") || 
                    lowerResponse.contains("<!doctype") || (!trimmedResponse.startsWith("{") && !trimmedResponse.startsWith("["))) {
                    logger.error("API返回HTML页面而不是JSON，可能是认证失败或API端点错误");
                    logger.error("响应前100字符: {}", trimmedResponse.length() > 100 ? trimmedResponse.substring(0, 100) : trimmedResponse);
                    logger.error("请检查：1) API密钥是否正确 2) API端点URL是否正确 3) API服务是否正常运行");
                    throw new RuntimeException("API返回HTML页面，可能是认证失败。请检查API密钥和端点配置");
                }
                
                try {
                    @SuppressWarnings("unchecked")
                    Map<String, Object> responseBody = objectMapper.readValue(responseBodyStr, Map.class);
                    
                    // 检查是否有data字段且不为空（参考Comfly_nano_banana_edit节点的响应处理）
                    Object dataObj = responseBody.get("data");
                    if (dataObj != null) {
                        // 检查data是否为列表且不为空
                        if (dataObj instanceof List) {
                            @SuppressWarnings("unchecked")
                            List<Map<String, Object>> dataList = (List<Map<String, Object>>) dataObj;
                            if (dataList.isEmpty()) {
                                logger.error("文生图任务提交失败: data字段为空列表");
                                throw new RuntimeException("任务提交失败: API返回空的data列表");
                            }
                        }
                        logger.info("文生图任务提交成功: {}", responseBody);
                        return responseBody;
                    } else {
                        logger.error("文生图任务提交失败: 响应中没有data字段，响应内容: {}", responseBody);
                        Object errorMessage = responseBody.get("message");
                        if (errorMessage != null) {
                            throw new RuntimeException("任务提交失败: " + errorMessage);
                        } else {
                            throw new RuntimeException("任务提交失败: API响应中没有data字段");
                        }
                    }
                } catch (com.fasterxml.jackson.core.JsonParseException e) {
                    logger.error("解析API响应为JSON失败，响应内容可能是HTML或其他格式", e);
                    logger.error("响应内容前200字符: {}", responseBodyStr.length() > 200 ? 
                               responseBodyStr.substring(0, 200) : responseBodyStr);
                    throw new RuntimeException("API返回非JSON响应，可能是认证失败。请检查API密钥和端点配置");
                }
            } else {
                logger.error("文生图任务提交失败，HTTP状态: {}", response.getStatus());
                throw new RuntimeException("任务提交失败，HTTP状态: " + response.getStatus());
            }

        } catch (UnirestException e) {
            logger.error("提交文生图任务异常", e);
            throw new RuntimeException("提交任务失败: " + e.getMessage());
        } catch (Exception e) {
            logger.error("提交文生图任务异常", e);
            throw new RuntimeException("提交任务失败: " + e.getMessage());
        }
    }

    /**
     * 提交图生图任务（使用banana模型，基于参考图片生成）
     * 参考 Comfly_nano_banana_edit 节点实现
     * 
     * @param prompt 提示词
     * @param imageBase64 参考图片Base64数据（带或不带 data:image 前缀）
     * @param aspectRatio 宽高比
     * @param hdMode 是否高清模式
     * @return API响应
     */
    public Map<String, Object> submitImageToImageTask(String prompt, String imageBase64, String aspectRatio, boolean hdMode) {
        return submitImageToImageTask(prompt, imageBase64, aspectRatio, hdMode, "nano-banana-2");
    }
    
    /**
     * 提交图生图任务（使用指定的模型）
     * @param imageModel 图像生成模型：nano-banana 或 nano-banana-2
     */
    public Map<String, Object> submitImageToImageTask(String prompt, String imageBase64, String aspectRatio, boolean hdMode, String imageModel) {
        // 参数验证：图生图必须有参考图片
        if (imageBase64 == null || imageBase64.isEmpty()) {
            logger.error("图生图任务失败：缺少参考图片");
            throw new IllegalArgumentException("图生图任务必须提供参考图片");
        }
        
        try {
            // 强制使用 nano-banana-2 模型
            String model = "nano-banana-2";
            logger.info("【调试】强制使用的模型参数: {}", model);
            
            logger.info("提交图生图任务: prompt={}, aspectRatio={}, hdMode={}, imageModel={}, imageBase64长度={}", 
                        prompt, aspectRatio, hdMode, model, imageBase64.length());
            
            // 直接使用模型对应的API端点，不使用系统配置的基础URL
            String url = "https://ai.comfly.chat" + "/v1/images/edits";
            logger.info("【调试】图生图API端点: {}", url);
            
            // 处理图片数据：如果是 URL，先下载转换为 base64
            String base64Data = imageBase64;
            if (imageBase64.startsWith("http://") || imageBase64.startsWith("https://")) {
                // 是 URL，需要下载图片
                logger.info("检测到图片URL，开始下载: {}", imageBase64.substring(0, Math.min(100, imageBase64.length())));
                try {
                    java.net.URL imageUrl = new java.net.URL(imageBase64);
                    java.net.HttpURLConnection conn = (java.net.HttpURLConnection) imageUrl.openConnection();
                    conn.setConnectTimeout(30000);
                    conn.setReadTimeout(60000);
                    try (java.io.InputStream is = conn.getInputStream();
                         java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream()) {
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        while ((bytesRead = is.read(buffer)) != -1) {
                            baos.write(buffer, 0, bytesRead);
                        }
                        byte[] imageData = baos.toByteArray();
                        base64Data = Base64.getEncoder().encodeToString(imageData);
                        logger.info("图片下载成功，大小: {} KB", imageData.length / 1024);
                    }
                } catch (Exception e) {
                    logger.error("下载图片失败: {}", e.getMessage());
                    throw new RuntimeException("下载参考图片失败: " + e.getMessage());
                }
            }
            
            // 去除 data:image/xxx;base64, 前缀
            if (base64Data.contains(",")) {
                base64Data = base64Data.substring(base64Data.indexOf(",") + 1);
            }
            byte[] imageBytes = Base64.getDecoder().decode(base64Data);
            logger.info("参考图片解码成功，字节数: {}", imageBytes.length);
            
            // 使用 multipart/form-data 格式（参考 Comfly_nano_banana_edit）
            HttpResponse<String> response = Unirest.post(url)
                .header("Authorization", "Bearer " + getEffectiveImageApiKey())
                .field("prompt", prompt)
                .field("model", model)
                .field("aspect_ratio", aspectRatio)
                .field("response_format", "url")
                .field("image", imageBytes, "image.png")
                .asString();
            logger.info("图生图API响应状态: {}", response.getStatus());
            String responseBodyStr = response.getBody();
            logger.info("图生图API响应内容（前500字符）: {}", responseBodyStr != null && responseBodyStr.length() > 500 ? 
                       responseBodyStr.substring(0, 500) : responseBodyStr);

            if (response.getStatus() == 200 && responseBodyStr != null) {
                // 检查响应是否为HTML
                String trimmedResponse = responseBodyStr.trim();
                String lowerResponse = trimmedResponse.toLowerCase();
                if (lowerResponse.startsWith("<!") || lowerResponse.startsWith("<html") || 
                    lowerResponse.contains("<!doctype") || (!trimmedResponse.startsWith("{") && !trimmedResponse.startsWith("["))) {
                    logger.error("图生图API返回HTML页面而不是JSON，可能是认证失败或API端点错误");
                    throw new RuntimeException("API返回HTML页面，可能是认证失败。请检查API密钥和端点配置");
                }
                
                try {
                    @SuppressWarnings("unchecked")
                    Map<String, Object> responseBody = objectMapper.readValue(responseBodyStr, Map.class);
                    
                    Object dataObj = responseBody.get("data");
                    if (dataObj != null) {
                        if (dataObj instanceof List) {
                            @SuppressWarnings("unchecked")
                            List<Map<String, Object>> dataList = (List<Map<String, Object>>) dataObj;
                            if (dataList.isEmpty()) {
                                logger.error("图生图任务提交失败: data字段为空列表");
                                throw new RuntimeException("任务提交失败: API返回空的data列表");
                            }
                        }
                        logger.info("图生图任务提交成功");
                        return responseBody;
                    } else {
                        logger.error("图生图任务提交失败: 响应中没有data字段");
                        Object errorMessage = responseBody.get("message");
                        if (errorMessage != null) {
                            throw new RuntimeException("任务提交失败: " + errorMessage);
                        } else {
                            throw new RuntimeException("任务提交失败: API响应中没有data字段");
                        }
                    }
                } catch (com.fasterxml.jackson.core.JsonParseException e) {
                    logger.error("解析图生图API响应为JSON失败", e);
                    throw new RuntimeException("API返回非JSON响应，可能是认证失败");
                }
            } else {
                logger.error("图生图任务提交失败，HTTP状态: {}", response.getStatus());
                throw new RuntimeException("任务提交失败，HTTP状态: " + response.getStatus());
            }

        } catch (UnirestException e) {
            logger.error("提交图生图任务异常", e);
            throw new RuntimeException("提交任务失败: " + e.getMessage());
        } catch (Exception e) {
            logger.error("提交图生图任务异常", e);
            throw new RuntimeException("提交任务失败: " + e.getMessage());
        }
    }

    /**
     * 将宽高比转换为图片尺寸
     * 参考Comfly项目的尺寸映射，支持分镜图生成的各种常用尺寸
     */
    private String convertAspectRatioToImageSize(String aspectRatio) {
        return switch (aspectRatio) {
            case "16:9" -> "1024x576";   // 横屏，适合宽屏分镜
            case "9:16" -> "576x1024";   // 竖屏，适合手机视频分镜
            case "4:3" -> "1024x768";    // 标准横屏
            case "3:4" -> "768x1024";    // 标准竖屏
            case "1:1" -> "1024x1024";   // 正方形
            default -> "1024x768";       // 默认标准横屏
        };
    }

    /**
     * 将宽高比转换为Sora2 API的size参数
     */
    private String convertAspectRatioToSize(String aspectRatio, boolean hdMode) {
        return switch (aspectRatio) {
            case "16:9" -> hdMode ? "1792x1024" : "1280x720";  // 1080P横屏 : 720P横屏
            case "9:16" -> hdMode ? "1024x1792" : "720x1280";  // 1080P竖屏 : 720P竖屏
            case "1:1" -> hdMode ? "1024x1024" : "720x720";   // 正方形
            default -> "720x1280";  // 默认竖屏
        };
    }

    /**
     * 优化提示词
     * 使用AI模型将用户输入的简单描述优化为详细、专业的提示词
     * 
     * @param prompt 原始提示词
     * @param type 优化类型：text-to-video, image-to-video, storyboard
     * @return 优化后的提示词，失败时返回原始提示词
     */
    public String optimizePrompt(String prompt, String type) {
        // 参数验证
        if (prompt == null || prompt.trim().isEmpty()) {
            logger.warn("提示词为空，无法优化");
            return prompt;
        }
        
        // 长度限制（防止过长提示词）
        if (prompt.length() > 1000) {
            logger.warn("提示词过长（{}字符），截取前1000字符", prompt.length());
            prompt = prompt.substring(0, 1000);
        }
        
        try {
            logger.info("开始优化提示词: prompt={}, type={}, length={}", 
                prompt.length() > 50 ? prompt.substring(0, 50) + "..." : prompt, 
                type, 
                prompt.length());
            
            // 从系统设置获取优化提示词的API端点和模型
            com.example.demo.model.SystemSettings settings = systemSettingsService.getOrCreate();
            
            // 使用默认系统提示词
            String systemPrompt = getOptimizationPrompt(type);
            logger.info("{}优化：使用默认系统提示词", type);
            
            // 如果是分镜图类型，将系统引导词拼接到用户提示词前面一起优化
            String promptToOptimize = prompt;
            if ("storyboard".equals(type)) {
                String storyboardSystemPrompt = settings.getStoryboardSystemPrompt();
                if (storyboardSystemPrompt != null && !storyboardSystemPrompt.trim().isEmpty()) {
                    promptToOptimize = storyboardSystemPrompt.trim() + ", " + prompt;
                    logger.info("分镜图优化：已拼接系统引导词，最终长度: {}", promptToOptimize.length());
                }
            }
            
            // 提示词优化使用统一的 API base URL
            String apiUrl = getEffectiveApiBaseUrl();
            String url = apiUrl + "/v1/chat/completions";
            
            String optimizationModel = settings.getPromptOptimizationModel();
            if (optimizationModel == null || optimizationModel.isEmpty()) {
                optimizationModel = "gpt-5.1-thinking"; // 默认模型
            }
            logger.info("使用API端点: {}, 模型: {} 进行提示词优化", apiUrl, optimizationModel);
            
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("model", optimizationModel);
            
            List<Map<String, String>> messages = new java.util.ArrayList<>();
            Map<String, String> systemMessage = new HashMap<>();
            systemMessage.put("role", "system");
            systemMessage.put("content", systemPrompt);
            messages.add(systemMessage);
            
            Map<String, String> userMessage = new HashMap<>();
            userMessage.put("role", "user");
            userMessage.put("content", "请优化以下提示词，保持原始意图：\n" + promptToOptimize);
            messages.add(userMessage);
            
            requestBody.put("messages", messages);
            requestBody.put("temperature", 0.7);
            // 分镜类型需要更多 tokens（JSON 包含 12 个镜头描述）
            int maxTokens = "storyboard".equals(type) ? 4000 : 800;
            requestBody.put("max_tokens", maxTokens);
            
            String requestBodyJson = objectMapper.writeValueAsString(requestBody);
            
            // 设置超时时间（60秒）
            HttpResponse<String> response = Unirest.post(url)
                .header("Authorization", "Bearer " + getEffectiveApiKey())
                .header("Content-Type", "application/json")
                .socketTimeout(60000)
                .connectTimeout(15000)
                .body(requestBodyJson)
                .asString();

            int statusCode = response.getStatus();
            logger.info("提示词优化API响应状态: {}", statusCode);
            
            if (statusCode == 200 && response.getBody() != null) {
                try {
                    @SuppressWarnings("unchecked")
                    Map<String, Object> responseBody = objectMapper.readValue(response.getBody(), Map.class);
                    
                    // 检查API错误响应
                    if (responseBody.containsKey("error")) {
                        @SuppressWarnings("unchecked")
                        Map<String, Object> error = (Map<String, Object>) responseBody.get("error");
                        String errorMessage = (String) error.get("message");
                        logger.error("API返回错误: {}", errorMessage);
                        return prompt; // 返回原始提示词
                    }
                    
                    @SuppressWarnings("unchecked")
                    List<Map<String, Object>> choices = (List<Map<String, Object>>) responseBody.get("choices");
                    if (choices != null && !choices.isEmpty()) {
                        @SuppressWarnings("unchecked")
                        Map<String, Object> choice = choices.get(0);
                        
                        // 检查finish_reason
                        String finishReason = (String) choice.get("finish_reason");
                        if ("length".equals(finishReason)) {
                            logger.warn("优化结果被截断，可能需要增加max_tokens");
                        }
                        
                        @SuppressWarnings("unchecked")
                        Map<String, Object> message = (Map<String, Object>) choice.get("message");
                        if (message != null) {
                            String optimizedPrompt = (String) message.get("content");
                            if (optimizedPrompt != null && !optimizedPrompt.trim().isEmpty()) {
                                optimizedPrompt = optimizedPrompt.trim();
                                // 移除可能的前后缀标记
                                optimizedPrompt = optimizedPrompt.replaceAll("^\"+|\"+$", "");
                                logger.info("提示词优化成功: 原始长度={}, 优化后长度={}", 
                                    prompt.length(), optimizedPrompt.length());
                                return optimizedPrompt;
                            }
                        }
                    }
                    
                    logger.warn("API响应格式异常，无法解析优化结果");
                } catch (Exception parseException) {
                    logger.error("解析API响应失败", parseException);
                }
            } else {
                logger.error("提示词优化API请求失败，HTTP状态: {}, 响应: {}", 
                    statusCode, 
                    response.getBody() != null && response.getBody().length() < 200 ? response.getBody() : "响应过长");
            }
            
            // 所有失败情况都返回原始提示词
            return prompt;
            
        } catch (com.fasterxml.jackson.core.JsonProcessingException e) {
            logger.error("提示词优化JSON处理失败", e);
            return prompt;
        } catch (kong.unirest.UnirestException e) {
            // Unirest异常可能包含超时或连接错误
            Throwable cause = e.getCause();
            if (cause instanceof java.net.SocketTimeoutException) {
                logger.error("提示词优化超时", e);
            } else if (cause instanceof java.net.ConnectException) {
                logger.error("提示词优化连接失败", e);
            } else {
                logger.error("提示词优化网络异常", e);
            }
            return prompt;
        } catch (Exception e) {
            logger.error("提示词优化异常", e);
            return prompt;
        }
    }

    /**
     * 多模态提示词优化（支持图片）
     * 将用户提示词和参考图片一起发送给大模型进行优化
     * 
     * @param prompt 原始提示词
     * @param type 优化类型
     * @param imageUrls 参考图片列表（Base64 或 URL）
     * @return 优化后的提示词
     */
    public String optimizePromptWithImages(String prompt, String type, List<String> imageUrls) {
        return optimizePromptWithImages(prompt, type, imageUrls, null);
    }
    
    /**
     * 多模态提示词优化（支持自定义系统提示词）
     * 
     * @param prompt 原始提示词
     * @param type 优化类型
     * @param imageUrls 参考图片列表（Base64 或 URL）
     * @param customSystemPrompt 自定义系统提示词（如果为null则使用默认）
     * @return 优化后的提示词
     */
    public String optimizePromptWithImages(String prompt, String type, List<String> imageUrls, String customSystemPrompt) {
        // 如果没有图片，直接调用普通方法
        if (imageUrls == null || imageUrls.isEmpty()) {
            return optimizePrompt(prompt, type);
        }
        
        try {
            logger.info("开始多模态提示词优化: prompt长度={}, type={}, 图片数量={}, 自定义系统提示词={}", 
                prompt.length(), type, imageUrls.size(), customSystemPrompt != null ? "有" : "无");
            
            com.example.demo.model.SystemSettings settings = systemSettingsService.getOrCreate();
            
            // 优先使用自定义系统提示词，否则使用默认
            String systemPrompt = (customSystemPrompt != null && !customSystemPrompt.trim().isEmpty()) 
                ? customSystemPrompt 
                : getOptimizationPrompt(type);
            
            // 提示词优化使用统一的 API base URL
            String apiUrl = getEffectiveApiBaseUrl();
            String url = apiUrl + "/v1/chat/completions";
            
            // 使用支持视觉的模型
            String optimizationModel = settings.getPromptOptimizationModel();
            if (optimizationModel == null || optimizationModel.isEmpty()) {
                optimizationModel = "gpt-4o-mini"; // 使用支持视觉的模型
            }
            logger.info("使用多模态模型: {}", optimizationModel);
            
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("model", optimizationModel);
            
            List<Map<String, Object>> messages = new java.util.ArrayList<>();
            
            // 系统消息
            Map<String, Object> systemMessage = new HashMap<>();
            systemMessage.put("role", "system");
            systemMessage.put("content", systemPrompt);
            messages.add(systemMessage);
            
            // 用户消息（多模态格式：文本+图片）
            Map<String, Object> userMessage = new HashMap<>();
            userMessage.put("role", "user");
            
            // 构建 content 数组（多模态格式）
            List<Map<String, Object>> contentArray = new java.util.ArrayList<>();
            
            // 添加文本部分（如果有自定义系统提示词，直接使用用户提示词；否则添加默认前缀）
            Map<String, Object> textContent = new HashMap<>();
            textContent.put("type", "text");
            if (customSystemPrompt != null && !customSystemPrompt.trim().isEmpty()) {
                // 使用自定义系统提示词时，直接发送用户提示词
                textContent.put("text", prompt);
            } else {
                // 使用默认系统提示词时，添加引导前缀
                textContent.put("text", "请根据以下用户需求和参考图片，优化生成专业的分镜提示词：\n\n" + prompt);
            }
            contentArray.add(textContent);
            
            // 添加图片部分（最多3张）
            int imageCount = Math.min(imageUrls.size(), 3);
            for (int i = 0; i < imageCount; i++) {
                String imageUrl = imageUrls.get(i);
                Map<String, Object> imageContent = new HashMap<>();
                imageContent.put("type", "image_url");
                
                Map<String, Object> imageUrlObj = new HashMap<>();
                if (imageUrl.startsWith("data:image")) {
                    // Base64 格式
                    imageUrlObj.put("url", imageUrl);
                } else if (imageUrl.startsWith("http")) {
                    // URL 格式
                    imageUrlObj.put("url", imageUrl);
                } else {
                    // 假设是纯 Base64，添加前缀
                    imageUrlObj.put("url", "data:image/png;base64," + imageUrl);
                }
                imageContent.put("image_url", imageUrlObj);
                contentArray.add(imageContent);
                logger.info("添加参考图片 {}/{}", i + 1, imageCount);
            }
            
            userMessage.put("content", contentArray);
            messages.add(userMessage);
            
            requestBody.put("messages", messages);
            requestBody.put("temperature", 0.7);
            int maxTokens = "storyboard".equals(type) ? 4000 : 800;
            requestBody.put("max_tokens", maxTokens);
            
            String requestBodyJson = objectMapper.writeValueAsString(requestBody);
            
            // 多模态需要更长的超时时间（120秒）
            HttpResponse<String> response = Unirest.post(url)
                .header("Authorization", "Bearer " + getEffectiveApiKey())
                .header("Content-Type", "application/json")
                .socketTimeout(120000)
                .connectTimeout(30000)
                .body(requestBodyJson)
                .asString();
            
            int statusCode = response.getStatus();
            logger.info("多模态提示词优化API响应状态: {}", statusCode);
            
            if (statusCode == 200 && response.getBody() != null) {
                @SuppressWarnings("unchecked")
                Map<String, Object> responseBody = objectMapper.readValue(response.getBody(), Map.class);
                
                @SuppressWarnings("unchecked")
                List<Map<String, Object>> choices = (List<Map<String, Object>>) responseBody.get("choices");
                if (choices != null && !choices.isEmpty()) {
                    @SuppressWarnings("unchecked")
                    Map<String, Object> choice = choices.get(0);
                    @SuppressWarnings("unchecked")
                    Map<String, Object> message = (Map<String, Object>) choice.get("message");
                    if (message != null) {
                        String optimizedPrompt = (String) message.get("content");
                        if (optimizedPrompt != null && !optimizedPrompt.trim().isEmpty()) {
                            optimizedPrompt = optimizedPrompt.trim();
                            optimizedPrompt = optimizedPrompt.replaceAll("^\"+|\"+$", "");
                            logger.info("多模态提示词优化成功: 原始长度={}, 优化后长度={}", 
                                prompt.length(), optimizedPrompt.length());
                            return optimizedPrompt;
                        }
                    }
                }
            } else {
                logger.error("多模态提示词优化失败: status={}", statusCode);
            }
        } catch (Exception e) {
            logger.error("多模态提示词优化异常", e);
        }
        
        // 失败时回退到普通方法
        logger.warn("多模态优化失败，回退到普通文本优化");
        return optimizePrompt(prompt, type);
    }

    /**
     * 根据类型获取优化提示词的系统指令
     * 参考Comfly项目的优化策略，使用更智能的提示词优化
     */
    private String getOptimizationPrompt(String type) {
        return switch (type) {
            case "text-to-video" -> """
                你是一个专业的AI视频生成提示词优化专家。你的任务是将用户提供的描述优化为详细、专业、适合AI视频生成的英文提示词。
                
                核心优化策略：
                1. 语言处理：
                   - 如果输入是中文，翻译成流畅、地道的英文
                   - 如果输入是英文，直接优化并增强表达
                   - 确保语义准确，不丢失原始意图
                
                2. 视觉细节增强：
                   - 场景环境：详细描述背景、地点、氛围（如：sunset beach, urban street, cozy cafe）
                   - 人物特征：外貌、服装、表情、姿态（如：young woman with long hair, wearing casual clothes）
                   - 动作细节：具体动作、运动轨迹、速度（如：walking slowly, running gracefully, dancing elegantly）
                   - 光线效果：光照方向、强度、色温（如：soft golden hour light, dramatic shadows, natural daylight）
                   - 色彩风格：色调、饱和度、风格（如：vibrant colors, muted tones, cinematic color grading）
                   - 构图方式：镜头类型、角度、景深（如：wide establishing shot, close-up, shallow depth of field）
                
                3. 专业术语使用：
                   - 电影术语：cinematic, wide shot, medium shot, close-up, extreme close-up, tracking shot, dolly shot
                   - 镜头运动：zoom in/out, pan left/right, tilt up/down, crane shot, aerial view
                   - 视觉效果：slow motion, time-lapse, smooth transition, dynamic movement
                
                4. 时间维度：
                   - 明确视频时长和节奏
                   - 描述动作的持续时间和变化
                   - 添加时间相关的视觉元素（如：day to night transition）
                
                5. 输出要求：
                   - 只输出优化后的提示词，不要添加任何说明、引号、标记或额外文字
                   - 提示词应该直接可用，长度控制在150-300词之间
                   - 保持专业、清晰、有画面感
                   - 确保每个细节都有助于AI准确理解并生成视频
                
                示例优化：
                输入："一个女孩在海边跑步"
                输出："A young woman running gracefully along a pristine beach at golden hour, soft warm sunlight casting long shadows on the sand, gentle ocean waves in the background, cinematic wide shot with shallow depth of field, vibrant colors, smooth camera movement following the subject, professional video quality"
                """;
            case "image-to-video" -> """
                你是一个专业的AI图生视频提示词优化专家。你的任务是将用户提供的描述优化为详细、专业、适合AI图生视频的英文提示词。
                
                核心优化策略：
                1. 语言处理：
                   - 如果输入是中文，翻译成流畅、地道的英文
                   - 如果输入是英文，直接优化并增强表达
                   - 确保语义准确，不丢失原始意图
                
                2. 动画效果重点：
                   - 镜头运动：zoom in/out, pan left/right, tilt up/down, tracking shot, dolly movement
                   - 动态效果：smooth transition, dynamic movement, fluid motion, natural flow
                   - 时间变化：slow motion, time-lapse, gradual change, progressive movement
                   - 运动方向：left to right, top to bottom, forward, backward, circular motion
                
                3. 转换需求：
                   - 描述从静态图片到动态视频的转换方式
                   - 明确哪些元素需要动起来（人物、物体、背景等）
                   - 描述运动的速度、节奏和变化
                   - 添加时间维度和动态效果描述
                
                4. 专业术语使用：
                   - 视频制作术语：smooth transition, dynamic movement, fluid animation, cinematic motion
                   - 镜头运动：zoom, pan, tilt, track, dolly, crane, aerial
                   - 视觉效果：fade in/out, cross dissolve, wipe, morph
                
                5. 输出要求：
                   - 只输出优化后的提示词，不要添加任何说明、引号、标记或额外文字
                   - 提示词应该直接可用，长度控制在100-250词之间
                   - 重点突出动画和运动效果
                   - 确保能准确表达图片到视频的转换需求
                
                示例优化：
                输入："让图片中的人物动起来"
                输出："Smooth and natural movement of the person in the image, gentle walking motion with fluid body movement, subtle camera pan following the subject, natural lighting and shadows, seamless animation transition from static to dynamic, professional video quality, smooth motion blur"
                """;
            case "storyboard" -> """
                你是一个专业的分镜图提示词优化专家。你的任务是将用户提供的描述优化为详细、专业、适合生成故事板分镜图的英文提示词。
                
                核心优化策略：
                1. 语言处理：
                   - 如果输入是中文，翻译成流畅、地道的英文
                   - 如果输入是英文，直接优化并增强表达
                   - 确保语义准确，不丢失原始意图
                
                2. 分镜图重点：
                   - 镜头构图：composition, framing, visual hierarchy, rule of thirds
                   - 画面布局：subject placement, background elements, foreground details
                   - 视觉元素：key visual elements, focal points, visual storytelling
                   - 场景描述：environment, setting, atmosphere, mood
                
                3. 专业分镜术语：
                   - 镜头类型：establishing shot, wide shot, medium shot, close-up, extreme close-up, over-the-shoulder shot
                   - 构图方式：rule of thirds, center composition, leading lines, symmetry, asymmetry
                   - 视角：eye level, high angle, low angle, bird's eye view, worm's eye view
                   - 景深：shallow depth of field, deep focus, bokeh effect
                
                4. 画面细节：
                   - 场景环境：详细描述背景、地点、氛围
                   - 人物/物体位置：明确主体和背景的位置关系
                   - 光线：光照方向、强度、色温、阴影
                   - 氛围：情绪、风格、色调
                
                5. 输出要求：
                   - 只输出优化后的提示词，不要添加任何说明、引号、标记或额外文字
                   - 提示词应该直接可用，长度控制在200-500词之间
                   - 适合生成专业分镜图风格
                   - 强调构图和画面元素，包含清晰的场景描述和视觉细节
                
                示例优化：
                输入："一个咖啡的广告"
                输出："Professional storyboard style, establishing shot of a modern coffee shop interior, warm ambient lighting, medium shot of a barista preparing coffee, close-up of coffee beans being ground, steam rising from espresso machine, shallow depth of field, cinematic framing, rule of thirds composition, vibrant colors, professional commercial photography style, detailed visual storytelling, coffee cup in foreground, blurred background, natural daylight through windows"
                """;
            default -> """
                你是一个专业的AI提示词优化专家。请将用户提供的简单描述优化为详细、专业的英文提示词。
                
                优化要求：
                1. 语言处理：将中文描述翻译成流畅的英文，如果已经是英文则直接优化
                2. 细节增强：添加必要的视觉细节和描述，使提示词更加具体和专业
                3. 保持意图：保持原始意图不变，只优化表达方式
                4. 专业术语：使用相关领域的专业术语和词汇
                5. 输出格式：只输出优化后的提示词，不要添加任何说明、引号、标记或额外文字
                6. 长度控制：优化后的提示词应该直接可用，长度控制在合理范围内
                """;
        };
    }

}
