import _ from "lodash";
import fs from "fs-extra";
import axios from "axios";

import APIException from "@/lib/exceptions/APIException.ts";

import EX from "@/api/consts/exceptions.ts";
import util from "@/lib/util.ts";
import { getCredit, receiveCredit, request, parseRegionFromToken, getAssistantId, checkImageContent, RegionInfo } from "./core.ts";
import logger from "@/lib/logger.ts";
import { SmartPoller, PollingStatus } from "@/lib/smart-poller.ts";
import { DEFAULT_ASSISTANT_ID_CN, DEFAULT_ASSISTANT_ID_US, DEFAULT_ASSISTANT_ID_HK, DEFAULT_ASSISTANT_ID_JP, DEFAULT_ASSISTANT_ID_SG, DEFAULT_VIDEO_MODEL, DRAFT_VERSION, DRAFT_VERSION_OMNI, OMNI_BENEFIT_TYPE, OMNI_BENEFIT_TYPE_FAST, VIDEO_MODEL_MAP, VIDEO_MODEL_MAP_US, VIDEO_MODEL_MAP_ASIA } from "@/api/consts/common.ts";
import { uploadImageBuffer, ImageUploadResult } from "@/lib/image-uploader.ts";
import { uploadVideoBuffer, VideoUploadResult } from "@/lib/video-uploader.ts";
import { extractVideoUrl, fetchHighQualityVideoUrl } from "@/lib/image-utils.ts";
import { uploadVideoFromUrl } from "@/lib/video-uploader.ts";

export const DEFAULT_MODEL = DEFAULT_VIDEO_MODEL;

export function getModel(model: string, regionInfo: RegionInfo) {
  // 根据站点选择不同的模型映射
  let modelMap: Record<string, string>;
  if (regionInfo.isUS) {
    modelMap = VIDEO_MODEL_MAP_US;
  } else if (regionInfo.isHK || regionInfo.isJP || regionInfo.isSG) {
    modelMap = VIDEO_MODEL_MAP_ASIA;
  } else {
    modelMap = VIDEO_MODEL_MAP;
  }
  return modelMap[model] || modelMap[DEFAULT_MODEL] || VIDEO_MODEL_MAP[DEFAULT_MODEL];
}

function getVideoBenefitType(model: string): string {
  // veo3.1 模型 (需先于 veo3 检查)
  if (model.includes("veo3.1")) {
    return "generate_video_veo3.1";
  }
  // veo3 模型
  if (model.includes("veo3")) {
    return "generate_video_veo3";
  }
  // sora2 模型
  if (model.includes("sora2")) {
    return "generate_video_sora2";
  }
  if (model.includes("40_pro")) {
    return "dreamina_video_seedance_20_pro";
  }
  if (model.includes("40")) {
    return "dreamina_seedance_20_fast";
  }
  if (model.includes("3.5_pro")) {
    return "dreamina_video_seedance_15_pro";
  }
  if (model.includes("3.5")) {
    return "dreamina_video_seedance_15";
  }
  return "basic_video_operation_vgfm_v_three";
}

// 处理本地上传的文件
async function uploadImageFromFile(file: any, refreshToken: string, regionInfo: RegionInfo): Promise<ImageUploadResult> {
  try {
    logger.info(`开始从本地文件上传视频图片: ${file.originalFilename} (路径: ${file.filepath})`);
    const imageBuffer = await fs.readFile(file.filepath);
    return await uploadImageBuffer(imageBuffer, refreshToken, regionInfo);
  } catch (error: any) {
    logger.error(`从本地文件上传视频图片失败: ${error.message}`);
    throw error;
  }
}

// 处理来自URL的图片
async function uploadImageFromUrl(imageUrl: string, refreshToken: string, regionInfo: RegionInfo): Promise<ImageUploadResult> {
  try {
    logger.info(`开始从URL下载并上传视频图片: ${imageUrl}`);
    const imageResponse = await axios.get(imageUrl, {
      responseType: 'arraybuffer',
      proxy: false,
    });
    if (imageResponse.status < 200 || imageResponse.status >= 300) {
      throw new Error(`下载图片失败: ${imageResponse.status}`);
    }
    const imageBuffer = imageResponse.data;
    return await uploadImageBuffer(imageBuffer, refreshToken, regionInfo);
  } catch (error: any) {
    logger.error(`从URL上传视频图片失败: ${error.message}`);
    throw error;
  }
}

/**
 * 解析 omni_reference 模式的 prompt，将 @引用 拆解为 meta_list
 * 输入: "@image_file_1作为首帧，@image_file_2作为尾帧，运动动作模仿@video_file"
 * 输出: 交替的 text + material_ref 段
 */
function parseOmniPrompt(prompt: string, materialRegistry: Map<string, any>): any[] {
  // 收集所有可识别的引用名（字段名 + 原始文件名），转义正则特殊字符
  const refNames = [...materialRegistry.keys()]
    .sort((a, b) => b.length - a.length) // 长名优先匹配
    .map(n => n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));

  if (refNames.length === 0) {
    return [{ meta_type: "text", text: prompt }];
  }

  const pattern = new RegExp(`@(${refNames.join('|')})`, 'g');
  const meta_list: any[] = [];
  let lastIndex = 0;
  let match: RegExpExecArray | null;

  while ((match = pattern.exec(prompt)) !== null) {
    // 文本段
    if (match.index > lastIndex) {
      const textSegment = prompt.slice(lastIndex, match.index);
      if (textSegment) {
        meta_list.push({ meta_type: "text", text: textSegment });
      }
    }
    // 引用段
    const refName = match[1];
    const entry = materialRegistry.get(refName);
    if (entry) {
      meta_list.push({
        meta_type: entry.type,
        text: "",
        material_ref: { material_idx: entry.idx },
      });
    }
    lastIndex = pattern.lastIndex;
  }

  // 尾部文本
  if (lastIndex < prompt.length) {
    meta_list.push({ meta_type: "text", text: prompt.slice(lastIndex) });
  }

  // 如果没有任何 @ 引用，把整个 prompt 作为文本段
  if (meta_list.length === 0) {
    meta_list.push({ meta_type: "text", text: prompt });
  }

  return meta_list;
}


/**
 * 生成视频
 *
 * @param _model 模型名称
 * @param prompt 提示词
 * @param options 选项
 * @param refreshToken 刷新令牌
 * @returns 视频URL
 */
export async function generateVideo(
  _model: string,
  prompt: string,
  {
    ratio = "1:1",
    resolution = "720p",
    duration = 5,
    filePaths = [],
    files = {},
    httpRequest,
    functionMode = "first_last_frames",
  }: {
    ratio?: string;
    resolution?: string;
    duration?: number;
    filePaths?: string[];
    files?: any;
    httpRequest?: any;
    functionMode?: string;
  },
  refreshToken: string
) {
  // 检测区域
  const regionInfo = parseRegionFromToken(refreshToken);
  const { isInternational } = regionInfo;

  logger.info(`视频生成区域检测: isInternational=${isInternational}`);

  const model = getModel(_model, regionInfo);
  const isVeo3 = model.includes("veo3");
  const isSora2 = model.includes("sora2");
  const is35Pro = model.includes("3.5_pro");
  const is40Pro = model.includes("40_pro");
  const is40 = model.includes("40") && !model.includes("40_pro");
  // 只有 video-3.0 和 video-3.0-fast 支持 resolution 参数（3.0-pro 和 3.5-pro 不支持）
  const supportsResolution = (model.includes("vgfm_3.0") || model.includes("vgfm_3.0_fast")) && !model.includes("_pro");

  // 将秒转换为毫秒
  // veo3 模型固定 8 秒
  // sora2 模型支持 4秒、8秒、12秒，默认4秒
  // 3.5-pro 模型支持 5秒、10秒、12秒，默认5秒
  // 4.0-pro (seedance 2.0) 和 4.0 (seedance 2.0-fast) 模型支持 4~15秒，默认5秒
  // 其他模型支持 5秒、10秒，默认5秒
  let durationMs: number;
  let actualDuration: number;
  if (isVeo3) {
    durationMs = 8000;
    actualDuration = 8;
  } else if (isSora2) {
    if (duration === 12) {
      durationMs = 12000;
      actualDuration = 12;
    } else if (duration === 8) {
      durationMs = 8000;
      actualDuration = 8;
    } else {
      durationMs = 4000;
      actualDuration = 4;
    }
  } else if (is40Pro || is40) {
    // seedance 2.0 和 2.0-fast: 支持 4~15 秒，clamp 到有效范围，默认 5 秒
    actualDuration = Math.max(4, Math.min(15, duration));
    durationMs = actualDuration * 1000;
  } else if (is35Pro) {
    if (duration === 12) {
      durationMs = 12000;
      actualDuration = 12;
    } else if (duration === 10) {
      durationMs = 10000;
      actualDuration = 10;
    } else {
      durationMs = 5000;
      actualDuration = 5;
    }
  } else {
    durationMs = duration === 10 ? 10000 : 5000;
    actualDuration = duration === 10 ? 10 : 5;
  }

  logger.info(`使用模型: ${_model} 映射模型: ${model} 比例: ${ratio} 分辨率: ${supportsResolution ? resolution : '不支持'} 时长: ${actualDuration}s`);

  // 检查积分
  const { totalCredit } = await getCredit(refreshToken);
  if (totalCredit <= 0) {
    logger.info("积分为 0，尝试收取今日积分...");
    try {
      await receiveCredit(refreshToken);
    } catch (receiveError) {
      logger.warn(`收取积分失败: ${receiveError.message}. 这可能是因为: 1) 今日已收取过积分, 2) 账户受到风控限制, 3) 需要在官网手动收取首次积分`);
      throw new APIException(EX.API_VIDEO_GENERATION_FAILED,
        `积分不足且无法自动收取。请访问即梦官网手动收取首次积分，或检查账户状态。`);
    }
  }

  const isOmniMode = functionMode === "omni_reference";

  // omni_reference 仅支持 seedance 2.0 (40_pro) 和 2.0-fast (40) 模型
  if (isOmniMode && !is40Pro && !is40) {
    throw new APIException(EX.API_REQUEST_FAILED,
      `omni_reference 模式仅支持 jimeng-video-seedance-2.0 和 jimeng-video-seedance-2.0-fast 模型`);
  }

  let requestData: any;

  if (isOmniMode) {
    // ========== omni_reference 分支 ==========
    logger.info(`进入 omni_reference 全能模式`);

    // 素材注册表: fieldName → { idx, type, uploadResult }
    interface MaterialEntry {
      idx: number;
      type: "image" | "video";
      fieldName: string;
      originalFilename: string;
      imageUri?: string;
      imageWidth?: number;
      imageHeight?: number;
      imageFormat?: string;
      videoResult?: VideoUploadResult;
    }
    const materialRegistry: Map<string, MaterialEntry> = new Map();
    let materialIdx = 0;

    // canonical key 集合，防止 originalFilename 覆盖
    const canonicalKeys = new Set<string>();
    canonicalKeys.add('image_file');
    canonicalKeys.add('video_file');
    for (let i = 1; i <= 9; i++) canonicalKeys.add(`image_file_${i}`);
    for (let i = 1; i <= 3; i++) canonicalKeys.add(`video_file_${i}`);

    // 安全注册别名：originalFilename 不与 canonical key 冲突时才注册
    function registerAlias(filename: string, entry: MaterialEntry) {
      if (!canonicalKeys.has(filename) && !materialRegistry.has(filename)) {
        materialRegistry.set(filename, entry);
      }
    }

    // 收集所有需要处理的图片和视频字段
    const imageFields: string[] = [];
    const videoFields: string[] = [];

    // 检测上传的文件
    if (files) {
      for (const fieldName of Object.keys(files)) {
        if (fieldName === 'image_file' || fieldName.startsWith('image_file_')) imageFields.push(fieldName);
        else if (fieldName === 'video_file' || fieldName.startsWith('video_file_')) videoFields.push(fieldName);
      }
    }

    // 检测URL字段
    for (let i = 1; i <= 9; i++) {
      const fieldName = `image_file_${i}`;
      if (typeof httpRequest?.body?.[fieldName] === 'string' && httpRequest.body[fieldName].startsWith('http')) {
        if (!imageFields.includes(fieldName)) imageFields.push(fieldName);
      }
    }
    for (let i = 1; i <= 3; i++) {
      const fieldName = `video_file_${i}`;
      if (typeof httpRequest?.body?.[fieldName] === 'string' && httpRequest.body[fieldName].startsWith('http')) {
        if (!videoFields.includes(fieldName)) videoFields.push(fieldName);
      }
    }
    // 检测不带数字后缀的裸名 URL 字段
    if (typeof httpRequest?.body?.image_file === 'string' && httpRequest.body.image_file.startsWith('http')) {
      if (!imageFields.includes('image_file')) imageFields.push('image_file');
    }
    if (typeof httpRequest?.body?.video_file === 'string' && httpRequest.body.video_file.startsWith('http')) {
      if (!videoFields.includes('video_file')) videoFields.push('video_file');
    }

    // 检查是否有素材
    const hasFilePaths = filePaths && filePaths.length > 0;
    if (imageFields.length === 0 && videoFields.length === 0 && !hasFilePaths) {
      throw new APIException(EX.API_REQUEST_FAILED,
        `omni_reference 模式需要至少上传一个素材文件 (image_file_*, video_file_*) 或提供素材URL`);
    }

    let totalVideoDuration = 0; // 累计视频时长

    // 串行上传图片素材
    for (const fieldName of imageFields) {
      const imageFile = files?.[fieldName];
      const imageUrlField = httpRequest?.body?.[fieldName];

      try {
        logger.info(`[omni] 上传 ${fieldName}`);
        let imgResult: ImageUploadResult;

        if (imageFile) {
          // 本地文件上传
          const buf = await fs.readFile(imageFile.filepath);
          imgResult = await uploadImageBuffer(buf, refreshToken, regionInfo);
          await checkImageContent(imgResult.uri, refreshToken, regionInfo);
          const entry: MaterialEntry = {
            idx: materialIdx++,
            type: "image",
            fieldName,
            originalFilename: imageFile.originalFilename,
            imageUri: imgResult.uri,
            imageWidth: imgResult.width,
            imageHeight: imgResult.height,
            imageFormat: imgResult.format,
          };
          materialRegistry.set(fieldName, entry);
          registerAlias(imageFile.originalFilename, entry);
          logger.info(`[omni] ${fieldName} 上传成功: ${imgResult.uri} (${imgResult.width}x${imgResult.height})`);
        } else if (imageUrlField && typeof imageUrlField === 'string' && imageUrlField.startsWith('http')) {
          // URL上传
          imgResult = await uploadImageFromUrl(imageUrlField, refreshToken, regionInfo);
          await checkImageContent(imgResult.uri, refreshToken, regionInfo);
          const entry: MaterialEntry = {
            idx: materialIdx++,
            type: "image",
            fieldName,
            originalFilename: imageUrlField,
            imageUri: imgResult.uri,
            imageWidth: imgResult.width,
            imageHeight: imgResult.height,
            imageFormat: imgResult.format,
          };
          materialRegistry.set(fieldName, entry);
          logger.info(`[omni] ${fieldName} URL上传成功: ${imgResult.uri} (${imgResult.width}x${imgResult.height})`);
        }
      } catch (error: any) {
        throw new APIException(EX.API_REQUEST_FAILED, `${fieldName} 处理失败: ${error.message}`);
      }
    }

    // 通过 filePaths 数组补充未被占用的图片槽位
    if (filePaths && filePaths.length > 0) {
      let slotIndex = 1;
      for (const url of filePaths) {
        // 找到第一个未被占用的槽位
        while (slotIndex <= 9 && materialRegistry.has(`image_file_${slotIndex}`)) {
          slotIndex++;
        }
        if (slotIndex > 9) break; // 已达到最大数量

        const fieldName = `image_file_${slotIndex}`;
        try {
          logger.info(`[omni] 从URL上传 ${fieldName}: ${url}`);
          const imgResult = await uploadImageFromUrl(url, refreshToken, regionInfo);
          await checkImageContent(imgResult.uri, refreshToken, regionInfo);
          const entry: MaterialEntry = {
            idx: materialIdx++,
            type: "image",
            fieldName,
            originalFilename: url,
            imageUri: imgResult.uri,
            imageWidth: imgResult.width,
            imageHeight: imgResult.height,
            imageFormat: imgResult.format,
          };
          materialRegistry.set(fieldName, entry);
          logger.info(`[omni] ${fieldName} URL上传成功: ${imgResult.uri} (${imgResult.width}x${imgResult.height})`);
        } catch (error: any) {
          throw new APIException(EX.API_REQUEST_FAILED, `${fieldName} URL图片处理失败: ${error.message}`);
        }
        slotIndex++;
      }
    }

    // 串行上传视频素材
    for (const fieldName of videoFields) {
      const videoFile = files?.[fieldName];
      const videoUrlField = httpRequest?.body?.[fieldName];

      try {
        logger.info(`[omni] 上传 ${fieldName}`);
        let vResult: VideoUploadResult;

        if (videoFile) {
          // 本地文件上传
          const buf = await fs.readFile(videoFile.filepath);
          vResult = await uploadVideoBuffer(buf, refreshToken, regionInfo);
          totalVideoDuration += vResult.videoMeta.duration;
          const entry: MaterialEntry = {
            idx: materialIdx++,
            type: "video",
            fieldName,
            originalFilename: videoFile.originalFilename,
            videoResult: vResult
          };
          materialRegistry.set(fieldName, entry);
          registerAlias(videoFile.originalFilename, entry);
          logger.info(`[omni] ${fieldName} 上传成功: vid=${vResult.vid}, ${vResult.videoMeta.width}x${vResult.videoMeta.height}, ${vResult.videoMeta.duration}s`);
        } else if (videoUrlField && typeof videoUrlField === 'string' && videoUrlField.startsWith('http')) {
          // URL上传
          vResult = await uploadVideoFromUrl(videoUrlField, refreshToken, regionInfo);
          totalVideoDuration += vResult.videoMeta.duration;
          const entry: MaterialEntry = {
            idx: materialIdx++,
            type: "video",
            fieldName,
            originalFilename: videoUrlField,
            videoResult: vResult
          };
          materialRegistry.set(fieldName, entry);
          logger.info(`[omni] ${fieldName} URL上传成功: vid=${vResult.vid}, ${vResult.videoMeta.width}x${vResult.videoMeta.height}, ${vResult.videoMeta.duration}s`);
        }
      } catch (error: any) {
        throw new APIException(EX.API_REQUEST_FAILED, `${fieldName} 处理失败: ${error.message}`);
      }
    }

    // 验证视频总时长
    const MAX_TOTAL_VIDEO_DURATION = 15;
    if (!Number.isFinite(totalVideoDuration)) {
      throw new APIException(EX.API_REQUEST_FAILED,
        `视频时长数据异常，请检查视频文件`);
    }
    if (totalVideoDuration > MAX_TOTAL_VIDEO_DURATION) {
      throw new APIException(EX.API_REQUEST_FAILED,
        `视频总时长 ${totalVideoDuration.toFixed(2)}s 超过限制 (最大 ${MAX_TOTAL_VIDEO_DURATION}s)`);
    }

    logger.info(`[omni] 视频总时长: ${totalVideoDuration.toFixed(2)}s`);

    // 构建 material_list（按注册顺序）
    const orderedEntries = [...new Map([...materialRegistry].filter(([k, v]) => k === v.fieldName)).values()]
      .sort((a, b) => a.idx - b.idx);

    const material_list: any[] = [];
    const materialTypes: number[] = [];

    for (const entry of orderedEntries) {
      if (entry.type === "image") {
        material_list.push({
          type: "",
          id: util.uuid(),
          material_type: "image",
          image_info: {
            type: "image",
            id: util.uuid(),
            source_from: "upload",
            platform_type: 1,
            name: "",
            image_uri: entry.imageUri,
            width: entry.imageWidth || 0,
            height: entry.imageHeight || 0,
            format: entry.imageFormat || "",
            uri: entry.imageUri,
          },
        });
        materialTypes.push(1);
      } else {
        const vm = entry.videoResult!;
        material_list.push({
          type: "",
          id: util.uuid(),
          material_type: "video",
          video_info: {
            type: "video",
            id: util.uuid(),
            source_from: "upload",
            name: "",
            vid: vm.vid,
            fps: 0,
            width: vm.videoMeta.width,
            height: vm.videoMeta.height,
            duration: Math.round(vm.videoMeta.duration * 1000),
          },
        });
        materialTypes.push(2);
      }
    }

    // 解析 prompt → meta_list
    const meta_list = parseOmniPrompt(prompt, materialRegistry);

    logger.info(`[omni] material_list: ${material_list.length} 项, meta_list: ${meta_list.length} 项, materialTypes: [${materialTypes}]`);

    // 构建 omni payload
    const componentId = util.uuid();
    const submitId = util.uuid();

    const sceneOption = {
      type: "video",
      scene: "BasicVideoGenerateButton",
      modelReqKey: model,
      videoDuration: actualDuration,
      materialTypes,
      reportParams: {
        enterSource: "generate",
        vipSource: "generate",
        extraVipFunctionKey: model,
        useVipFunctionDetailsReporterHoc: true,
      },
    };

    const metricsExtra = JSON.stringify({
      position: "page_bottom_box",
      isDefaultSeed: 1,
      originSubmitId: submitId,
      isRegenerate: false,
      enterFrom: "click",
      functionMode: "omni_reference",
      sceneOptions: JSON.stringify([sceneOption]),
    });

    // 根据模型选择 benefit_type
    const omniBenefitType = is40 ? OMNI_BENEFIT_TYPE_FAST : OMNI_BENEFIT_TYPE;

    requestData = {
      params: {
        aigc_features: "app_lip_sync",
        web_version: "7.5.0",
        da_version: DRAFT_VERSION_OMNI,
      },
      data: {
        extend: {
          root_model: model,
          m_video_commerce_info: {
            benefit_type: omniBenefitType,
            resource_id: "generate_video",
            resource_id_type: "str",
            resource_sub_type: "aigc",
          },
          m_video_commerce_info_list: [{
            benefit_type: omniBenefitType,
            resource_id: "generate_video",
            resource_id_type: "str",
            resource_sub_type: "aigc",
          }],
        },
        submit_id: submitId,
        metrics_extra: metricsExtra,
        draft_content: JSON.stringify({
          type: "draft",
          id: util.uuid(),
          min_version: DRAFT_VERSION_OMNI,
          min_features: ["AIGC_Video_UnifiedEdit"],
          is_from_tsn: true,
          version: DRAFT_VERSION_OMNI,
          main_component_id: componentId,
          component_list: [{
            type: "video_base_component",
            id: componentId,
            min_version: "1.0.0",
            aigc_mode: "workbench",
            metadata: {
              type: "",
              id: util.uuid(),
              created_platform: 3,
              created_platform_version: "",
              created_time_in_ms: Date.now().toString(),
              created_did: "",
            },
            generate_type: "gen_video",
            abilities: {
              type: "",
              id: util.uuid(),
              gen_video: {
                id: util.uuid(),
                type: "",
                text_to_video_params: {
                  type: "",
                  id: util.uuid(),
                  video_gen_inputs: [{
                    type: "",
                    id: util.uuid(),
                    min_version: DRAFT_VERSION_OMNI,
                    prompt: "",
                    video_mode: 2,
                    fps: 24,
                    duration_ms: durationMs,
                    unified_edit_input: {
                      type: "",
                      id: util.uuid(),
                      material_list,
                      meta_list,
                    },
                    idip_meta_list: [],
                  }],
                  video_aspect_ratio: ratio,
                  seed: Math.floor(Math.random() * 4294967296),
                  model_req_key: model,
                  priority: 0,
                },
                video_task_extra: metricsExtra,
              },
            },
            process_type: 1,
          }],
        }),
        http_common_info: {
          aid: getAssistantId(regionInfo),
        },
      },
    };
  } else {
    // ========== first_last_frames 分支（原有逻辑） ==========
    let first_frame_image = undefined;
    let end_frame_image = undefined;
    let uploadIDs: string[] = [];

    // 优先处理本地上传的文件
    const uploadedFiles = _.values(files);
    if (uploadedFiles && uploadedFiles.length > 0) {
      logger.info(`检测到 ${uploadedFiles.length} 个本地上传文件，优先处理`);
      for (let i = 0; i < uploadedFiles.length; i++) {
        const file = uploadedFiles[i];
        if (!file) continue;
        try {
          logger.info(`开始上传第 ${i + 1} 张本地图片: ${file.originalFilename}`);
          const imgResult = await uploadImageFromFile(file, refreshToken, regionInfo);
          if (imgResult) {
            await checkImageContent(imgResult.uri, refreshToken, regionInfo);
            uploadIDs.push(imgResult.uri);
            logger.info(`第 ${i + 1} 张本地图片上传成功: ${imgResult.uri}`);
          } else {
            logger.error(`第 ${i + 1} 张本地图片上传失败: 未获取到 image_uri`);
          }
        } catch (error: any) {
          logger.error(`第 ${i + 1} 张本地图片上传失败: ${error.message}`);
          if (i === 0) {
            throw new APIException(EX.API_REQUEST_FAILED, `首帧图片上传失败: ${error.message}`);
          }
        }
      }
    } else if (filePaths && filePaths.length > 0) {
      logger.info(`未检测到本地上传文件，处理 ${filePaths.length} 个图片URL`);
      for (let i = 0; i < filePaths.length; i++) {
        const filePath = filePaths[i];
        if (!filePath) {
          logger.warn(`第 ${i + 1} 个图片URL为空，跳过`);
          continue;
        }
        try {
          logger.info(`开始上传第 ${i + 1} 个URL图片: ${filePath}`);
          const imgResult = await uploadImageFromUrl(filePath, refreshToken, regionInfo);
          if (imgResult) {
            await checkImageContent(imgResult.uri, refreshToken, regionInfo);
            uploadIDs.push(imgResult.uri);
            logger.info(`第 ${i + 1} 个URL图片上传成功: ${imgResult.uri}`);
          } else {
            logger.error(`第 ${i + 1} 个URL图片上传失败: 未获取到 image_uri`);
          }
        } catch (error: any) {
          logger.error(`第 ${i + 1} 个URL图片上传失败: ${error.message}`);
          if (i === 0) {
            throw new APIException(EX.API_REQUEST_FAILED, `首帧图片上传失败: ${error.message}`);
          }
        }
      }
    } else {
      logger.info(`未提供图片文件或URL，将进行纯文本视频生成`);
    }

    if (uploadIDs.length > 0) {
      logger.info(`图片上传完成，共成功 ${uploadIDs.length} 张`);
      if (uploadIDs[0]) {
        first_frame_image = {
          format: "", height: 0, id: util.uuid(), image_uri: uploadIDs[0],
          name: "", platform_type: 1, source_from: "upload", type: "image", uri: uploadIDs[0], width: 0,
        };
        logger.info(`设置首帧图片: ${uploadIDs[0]}`);
      }
      if (uploadIDs[1]) {
        end_frame_image = {
          format: "", height: 0, id: util.uuid(), image_uri: uploadIDs[1],
          name: "", platform_type: 1, source_from: "upload", type: "image", uri: uploadIDs[1], width: 0,
        };
        logger.info(`设置尾帧图片: ${uploadIDs[1]}`);
      }
    }

    const componentId = util.uuid();
    const originSubmitId = util.uuid();
    const flFunctionMode = "first_last_frames";

    const sceneOption = {
      type: "video",
      scene: "BasicVideoGenerateButton",
      ...(supportsResolution ? { resolution } : {}),
      modelReqKey: model,
      videoDuration: actualDuration,
      materialTypes: [] as number[],
      reportParams: {
        enterSource: "generate",
        vipSource: "generate",
        extraVipFunctionKey: supportsResolution ? `${model}-${resolution}` : model,
        useVipFunctionDetailsReporterHoc: true,
      },
    };

    const metricsExtra = JSON.stringify({
      promptSource: "custom",
      isDefaultSeed: 1,
      originSubmitId,
      isRegenerate: false,
      enterFrom: "use_bgimage_prompt",
      position: "page_bottom_box",
      functionMode: flFunctionMode,
      sceneOptions: JSON.stringify([sceneOption]),
    });

    const hasImageInput = uploadIDs.length > 0;
    if (hasImageInput && ratio !== "1:1") {
      logger.warn(`图生视频模式下，ratio参数将被忽略（由输入图片的实际比例决定），但resolution参数仍然有效`);
    }

    logger.info(`视频生成模式: ${uploadIDs.length}张图片 (首帧: ${!!first_frame_image}, 尾帧: ${!!end_frame_image}), resolution: ${resolution}`);

    requestData = {
      params: {
        aigc_features: "app_lip_sync",
        web_version: "7.5.0",
        da_version: DRAFT_VERSION,
      },
      data: {
        extend: {
          root_model: model,
          m_video_commerce_info: {
            benefit_type: getVideoBenefitType(model),
            resource_id: "generate_video",
            resource_id_type: "str",
            resource_sub_type: "aigc",
          },
          m_video_commerce_info_list: [{
            benefit_type: getVideoBenefitType(model),
            resource_id: "generate_video",
            resource_id_type: "str",
            resource_sub_type: "aigc",
          }],
        },
        submit_id: util.uuid(),
        metrics_extra: metricsExtra,
        draft_content: JSON.stringify({
          type: "draft",
          id: util.uuid(),
          min_version: "3.0.5",
          min_features: [],
          is_from_tsn: true,
          version: DRAFT_VERSION,
          main_component_id: componentId,
          component_list: [{
            type: "video_base_component",
            id: componentId,
            min_version: "1.0.0",
            aigc_mode: "workbench",
            metadata: {
              type: "",
              id: util.uuid(),
              created_platform: 3,
              created_platform_version: "",
              created_time_in_ms: Date.now().toString(),
              created_did: "",
            },
            generate_type: "gen_video",
            abilities: {
              type: "",
              id: util.uuid(),
              gen_video: {
                id: util.uuid(),
                type: "",
                text_to_video_params: {
                  type: "",
                  id: util.uuid(),
                  video_gen_inputs: [{
                    type: "",
                    id: util.uuid(),
                    min_version: "3.0.5",
                    prompt,
                    video_mode: 2,
                    fps: 24,
                    duration_ms: durationMs,
                    ...(supportsResolution ? { resolution } : {}),
                    first_frame_image,
                    end_frame_image,
                    idip_meta_list: [],
                  }],
                  video_aspect_ratio: ratio,
                  seed: Math.floor(Math.random() * 4294967296),
                  model_req_key: model,
                  priority: 0,
                },
                video_task_extra: metricsExtra,
              },
            },
            process_type: 1,
          }],
        }),
        http_common_info: {
          aid: getAssistantId(regionInfo),
        },
      },
    };
  }

  // 发送请求
  const videoReferer = regionInfo.isCN
    ? "https://jimeng.jianying.com/ai-tool/generate?type=video"
    : "https://dreamina.capcut.com/ai-tool/generate?type=video";
  const { aigc_data } = await request(
    "post",
    "/mweb/v1/aigc_draft/generate",
    refreshToken,
    {
      ...requestData,
      headers: { Referer: videoReferer },
    }
  );

  const historyId = aigc_data.history_record_id;
  if (!historyId)
    throw new APIException(EX.API_IMAGE_GENERATION_FAILED, "记录ID不存在");

  logger.info(`视频生成任务已提交，history_id: ${historyId}，等待生成完成...`);

  // 首次查询前等待，让服务器有时间处理请求
  await new Promise((resolve) => setTimeout(resolve, 5000));

  // 使用 SmartPoller 进行智能轮询
  const maxPollCount = 900; // 增加轮询次数，支持更长的生成时间
  let pollAttempts = 0;

  const poller = new SmartPoller({
    maxPollCount,
    pollInterval: 20000, // 20秒基础间隔
    expectedItemCount: 1,
    type: 'video',
    timeoutSeconds: 3600 // 60分钟超时
  });

  const { result: pollingResult, data: finalHistoryData } = await poller.poll(async () => {
    pollAttempts++;

    // 使用标准API请求方式
    const result = await request("post", "/mweb/v1/get_history_by_ids", refreshToken, {
      data: {
        history_ids: [historyId],
      },
    });

    // 检查响应中是否有该 history_id 的数据
    // 由于 API 存在最终一致性，早期轮询可能暂时获取不到记录，返回处理中状态继续轮询
    if (!result[historyId]) {
      logger.warn(`API未返回历史记录 (轮询第${pollAttempts}次)，historyId: ${historyId}，继续等待...`);
      return {
        status: {
          status: 20, // PROCESSING
          itemCount: 0,
          historyId
        } as PollingStatus,
        data: { status: 20, item_list: [] }
      };
    }

    const historyData = result[historyId];

    const currentStatus = historyData.status;
    const currentFailCode = historyData.fail_code;
    const currentItemList = historyData.item_list || [];
    const finishTime = historyData.task?.finish_time || 0;

    // 记录详细信息
    if (currentItemList.length > 0) {
      const tempVideoUrl = currentItemList[0]?.common_attr?.transcoded_video?.origin?.video_url ||
                          currentItemList[0]?.video?.transcoded_video?.origin?.video_url ||
                          currentItemList[0]?.video?.play_url ||
                          currentItemList[0]?.video?.download_url ||
                          currentItemList[0]?.video?.url;
      if (tempVideoUrl) {
        logger.info(`检测到视频URL: ${tempVideoUrl}`);
      }
    }

    return {
      status: {
        status: currentStatus,
        failCode: currentFailCode,
        itemCount: currentItemList.length,
        finishTime,
        historyId
      } as PollingStatus,
      data: historyData
    };
  }, historyId);

  const item_list = finalHistoryData.item_list || [];

  // 尝试通过 get_local_item_list 获取高质量视频下载URL
  const itemId = item_list?.[0]?.item_id
    || item_list?.[0]?.id
    || item_list?.[0]?.local_item_id
    || item_list?.[0]?.common_attr?.id;

  if (itemId) {
    try {
      const hqVideoUrl = await fetchHighQualityVideoUrl(String(itemId), refreshToken);
      if (hqVideoUrl) {
        logger.info(`视频生成成功（高质量），URL: ${hqVideoUrl}，总耗时: ${pollingResult.elapsedTime}秒`);
        return hqVideoUrl;
      }
    } catch (error) {
      logger.warn(`获取高质量视频URL失败，将使用预览URL作为回退: ${error.message}`);
    }
  } else {
    logger.warn(`未能从item_list中提取item_id，将使用预览URL。item_list[0]键: ${item_list?.[0] ? Object.keys(item_list[0]).join(', ') : '无'}`);
  }

  // 回退：提取预览视频URL
  let fallbackVideoUrl = item_list?.[0] ? extractVideoUrl(item_list[0]) : null;

  // 如果无法获取视频URL，抛出异常
  if (!fallbackVideoUrl) {
    logger.error(`未能获取视频URL，item_list: ${JSON.stringify(item_list)}`);
    throw new APIException(EX.API_IMAGE_GENERATION_FAILED, "未能获取视频URL，请稍后查看");
  }

  logger.info(`视频生成成功，URL: ${fallbackVideoUrl}，总耗时: ${pollingResult.elapsedTime}秒`);
  return fallbackVideoUrl;
}
