package com.ai.da.service.impl;

import cn.hutool.core.img.gif.AnimatedGifEncoder;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ai.da.common.config.exception.BusinessException;
import com.ai.da.common.constant.CommonConstant;
import com.ai.da.common.constant.ModelConstants;
import com.ai.da.common.context.UserContext;
import com.ai.da.common.enums.*;
import com.ai.da.common.response.ResultEnum;
import com.ai.da.common.utils.*;
import com.ai.da.mapper.primary.*;
import com.ai.da.mapper.primary.entity.*;
import com.ai.da.model.dto.*;
import com.ai.da.model.enums.CollectionType;
import com.ai.da.model.enums.Module;
import com.ai.da.model.enums.SketchStyle;
import com.ai.da.model.vo.*;
import com.ai.da.python.PythonService;
import com.ai.da.service.*;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesis;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisParam;
import com.alibaba.dashscope.aigc.imagesynthesis.ImageSynthesisResult;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.JsonUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.gson.Gson;
import com.volcengine.ark.runtime.model.images.generation.GenerateImagesRequest;
import com.volcengine.ark.runtime.model.images.generation.ImagesResponse;
import com.volcengine.ark.runtime.service.ArkService;
import io.minio.errors.MinioException;
import io.netty.util.internal.StringUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.ai.da.common.enums.CollectionLevel1TypeEnum.*;
import static com.ai.da.common.enums.CreditsEventsEnum.PATTERN;
import static com.ai.da.common.enums.CreditsEventsEnum.POSE_TRANSFORMATION;
import static com.ai.da.common.enums.CreditsEventsEnum.TO_PRODUCT_IMAGE;
import static com.ai.da.common.enums.CreditsEventsEnum.TO_PRODUCT_IMAGE_ADVANCED;
import static com.ai.da.common.enums.WangXiangTaskStatusEnum.FAILED;
import static com.ai.da.common.enums.WangXiangTaskStatusEnum.UNKNOWN_W;

@Slf4j
@Service
@RequiredArgsConstructor
public class GenerateServiceImpl extends ServiceImpl<GenerateMapper, Generate> implements GenerateService {

    private final AccountService accountService;
    private final APIGenerateService apiGenerateService;
    private final CollectionElementMapper collectionElementMapper;
    private final CollectionElementService collectionElementService;
    private final CreditsService creditsService;
    private final CollectionSortMapper collectionSortMapper;
    private final CollectionSortService collectionSortService;
    private final CloudTaskMapper cloudTaskMapper;
    private final CloudTaskService cloudTaskService;
    private final GenerateCancelMapper generateCancelMapper;
    private final GenerateDetailMapper generateDetailMapper;
    private final LibraryService libraryService;
    //    private final LibraryModelPointService libraryModelPointService;
    private final MinioUtil minioUtil;
    private final MessageCenterService messageCenterService;
    private final PythonService pythonService;
    private final ProjectService projectService;
    private final PoseTransformationMapper poseTransformationMapper;
    private final RabbitMQService rabbitMQService;
    private final RedisUtil redisUtil;
    private final SketchReconstructionMapper sketchReconstructionMapper;
    private final SendRequestUtil sendRequestUtil;
    private final SysFileService sysFileService;
    private final ToProductImageResultMapper toProductImageResultMapper;
    private final ToProductImageResultService toProductImageResultService;
    @Value("${redis.key.orderForGenerate}")
    private String consumptionOrderKey;

    @Value("${redis.key.generateCancelSet}")
    private String cancelSetKey;

    @Value("${redis.key.generateExceptionMap}")
    private String exceptionMapKey;

    @Value("${redis.key.generateResult}")
    private String generateResultKey;

    @Value("${minio.bucketName.slogan}")
    private String sloganBucket;

    @Value("${minio.bucketName.users}")
    private String userBucket;

    @Value("${redis.key.relightResultKey}")
    private String relightResultKey;

    @Value("${redis.key.toProductImageResultKey}")
    private String toProductImageResultKey;

    @Value("${access.python.generate_sr_port}")
    private String generateServicePort;

    @Value("${ALIYUN_API_KEY}")
    private String ALIYUN_API_KEY;

    @Value("${DOUBAO_API_KEY}")
    private String DOUBAO_API_KEY;


    @Value("${FREEPIK_API_KEY}")
    private String FREEPIK_API_KEY;


    // 创建 Random 对象
    Random random = new Random();

    @Override
    public GenerateCaptionVO generateCaption(Long sketchElementId) {
        CollectionElement collectionElement = collectionElementMapper.selectById(sketchElementId);
        if (Objects.isNull(collectionElement)) {
            throw new BusinessException("the.image.does.not.exist.please.reselect");
        }
        String url = collectionElement.getUrl();
//        String caption = pythonService.generateSketchCaption(url);
        GenerateCaptionVO recognized_caption = new GenerateCaptionVO("recognized caption");

        return recognized_caption;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void generateThroughImageText(GenerateThroughImageTextDTO generateThroughImageTextDTO) {
        // 1、获取用户信息
        Long accountId = generateThroughImageTextDTO.getUserId();

        GenerateModeEnum modeEnum = getMode(generateThroughImageTextDTO);
        String generateType = modeEnum.getValue();
        // 2、判断必须入参是否为非空(在prepare阶段已校验)
        Generate generate = new Generate();
        generate.setAccountId(accountId);
        generate.setUniqueId(generateThroughImageTextDTO.getUniqueId());
        generate.setLevel1Type(generateThroughImageTextDTO.getLevel1Type());
        generate.setLevel2Type(generateThroughImageTextDTO.getLevel2Type());
        generate.setSeed(StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getSeed()) ? "" : generateThroughImageTextDTO.getSeed());
        // 当level1type是sketchboard时，存数据库需要加上当前性别
        generate.setGenerateType(generate.getLevel1Type().equals(SKETCH_BOARD.getRealName()) ?
                generateType + " (" + generateThroughImageTextDTO.getGender() + ")" :
                generateType);
        generate.setModelName(StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getModelName()) ? ModelNameEnum.MODEL_0.getCode() : generateThroughImageTextDTO.getModelName());
        generate.setCreateDate(DateUtil.getByTimeZone(generateThroughImageTextDTO.getTimeZone()));
        generate.setElementSource(StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getDesignType()) ? null : generateThroughImageTextDTO.getDesignType());

        String text = generateThroughImageTextDTO.getText();
        generate.setText(text);
        Long elementId = generateThroughImageTextDTO.getCollectionElementId();
//        validateGeneraType(generate, text, elementId);
        if (!StringUtil.isNullOrEmpty(text)) {
            text = modifyPrompt(text, generate, generateThroughImageTextDTO.getLevel1Type(), generateThroughImageTextDTO.getAgeGroup());
        }

        // todo 这一步现在还是有必要的吗？
        // 2.1 sketch或print在t_collection_element表/t_library表中的信息是否需要更新 如 level2Type
        CollectionElement collectionElement = collectionElementService.editLevel2Type(elementId, generateThroughImageTextDTO.getLevel2Type(), generateThroughImageTextDTO.getDesignType());

        // 3、向模型发起请求
        String mode = modeEnum.getType();
        String category = generateThroughImageTextDTO.getLevel1Type().equals(SKETCH_BOARD.getRealName()) ? "sketch" :
                generateThroughImageTextDTO.getLevel1Type().equals(PRINT_BOARD.getRealName()) ? "print" : "moodboard";
        String path = CommonConstant.GENERATE_PATH;
        String port = generateServicePort;
        String jsonString = "";
        HashMap<String, String> params = new HashMap<>();
        String version = null;
        if (!StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getModelName()) && generateThroughImageTextDTO.getModelName().equals("high")) {
            version = "high";
            params.put("version", "high");
        } else if (!StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getModelName()) && generateThroughImageTextDTO.getModelName().equals("normal")) {
            version = "fast";
            params.put("version", "fast");
        }
        // 3.1 确定不同类型的印花分别调哪个接口
        if (generateThroughImageTextDTO.getLevel1Type().equals(PRINT_BOARD.getRealName())) {
            switch (generateThroughImageTextDTO.getLevel2Type()) {
                case "Logo":
                    path = CommonConstant.GENERATE_SINGLE_LOGO;
                    params.put("tasks_id", generateThroughImageTextDTO.getUniqueId());
                    params.put("prompt", text);
                    params.put("seed", generateThroughImageTextDTO.getSeed());
                    jsonString = JSON.toJSONString(params, SerializerFeature.WriteMapNullValue);
                    break;
                case "Slogan":
                    path = CommonConstant.GENERATE_SLOGAN;
                    port = CommonConstant.PYTHON_PORT_9997;
                    params.put("num_point", "16");
                    params.put("tasks_id", generateThroughImageTextDTO.getUniqueId());
                    params.put("prompt", text);
                    params.put("image_url", collectionElement.getUrl());
                    jsonString = JSON.toJSONString(params, SerializerFeature.WriteMapNullValue);
                    break;
                case "Pattern":
                    GenerateToPythonDTO generateToPythonDTO = new GenerateToPythonDTO(generateThroughImageTextDTO.getUniqueId(), text, Objects.isNull(collectionElement) ? "" : collectionElement.getUrl(),
                            mode, category, generateThroughImageTextDTO.getGender(), version);
                    jsonString = JSON.toJSONString(generateToPythonDTO, SerializerFeature.WriteMapNullValue);
            }
        } else {
            GenerateToPythonDTO generateToPythonDTO = new GenerateToPythonDTO(generateThroughImageTextDTO.getUniqueId(), text, Objects.isNull(collectionElement) ? "" : collectionElement.getUrl(),
                    mode, category, generateThroughImageTextDTO.getGender(), version);
            jsonString = JSON.toJSONString(generateToPythonDTO, SerializerFeature.WriteMapNullValue);
        }

        Boolean requestResult = pythonService.generateSketchOrPrint(jsonString, port, path);

        // 4、将请求信息落库,将本次generate的请求信息添加到t_generate表中
        save(generate);

        // 5、将本次请求存入redis
        String key = generateResultKey + ":" + generateThroughImageTextDTO.getUniqueId();
        String status;
        if (requestResult) {
            status = "Executing";
        } else {
            status = "Fail";
        }
        GenerateResultVO generateResultVO = new GenerateResultVO(generateThroughImageTextDTO.getUniqueId(), null, null, status);
        redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

    }

    public GenerateModeEnum getMode(GenerateThroughImageTextDTO generateThroughImageTextDTO) {
        if (!StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getText())) {
            if (Objects.nonNull(generateThroughImageTextDTO.getCollectionElementId())) {
                return GenerateModeEnum.TEXT_IMAGE;
            } else {
                return GenerateModeEnum.TEXT;
            }
        } else {
            if (Objects.nonNull(generateThroughImageTextDTO.getCollectionElementId())) {
                return GenerateModeEnum.IMAGE;
            }
        }
        return GenerateModeEnum.TEXT;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processGenerateResult(String taskId, String url, String category) {
        // 1、处理模型返回的数据
        GenerateDetail generateDetail = new GenerateDetail();
        GenerateCollectionItemVO generateCollectionItemVO = new GenerateCollectionItemVO();
        Generate generate;
        try {
            generate = selectByUniqueId(taskId);
        } catch (MybatisPlusException e) {
            log.error(e.getMessage());
            if (e.getMessage().equals("One record is expected, but the query result is multiple records")) {
                generate = selectListByUniqueId(taskId).get(0);
            } else {
                log.error("There are some problems with database query, please try again.");
                return;
            }
        }
//        Generate generate = selectByUniqueId(taskId);
        String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(url, 24 * 60), Boolean.FALSE);
        // 通过MD5值和level1Type,判断不同level1Type下相同的图片是否被like过
        List<Map<String, Long>> libraryIdList = generateDetailMapper.getLibraryIdThroughMD5(md5, generate.getLevel1Type());
        if (!libraryIdList.isEmpty()) {
            generateDetail.setIsLike((byte) 1);
            generateDetail.setLibraryId(libraryIdList.get(0).get("library_id"));
            generateCollectionItemVO.setIsLiked(Boolean.TRUE);
        }
        generateDetail.setUrl(url);
        generateDetail.setGenerateId(generate.getId());
        generateDetail.setCreateDate(LocalDateTime.now());
        generateDetail.setMd5(md5);
        // 将相应的url保存到数据库
        generateDetailMapper.insert(generateDetail);

//        String uuid = taskId.substring(0, taskId.substring(0, taskId.lastIndexOf("-")).lastIndexOf("-"));
        String key = generateResultKey + ":" + taskId;
        String imageName = url.substring(url.lastIndexOf("/") + 1);
        String status = imageName.equals("white_image.jpg") ? "Invalid" : "Success";
        if (StringUtil.isNullOrEmpty(category)) {
            Generate generateRecord = selectByUniqueId(taskId);
            category = generateRecord.getLevel2Type();
        }
        GenerateResultVO generateResultVO = new GenerateResultVO(taskId, generateDetail.getId(), url, status, category);
        // 更新redis
        redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

        // 执行积分扣除
        // ** 注：如果生成的图片都是空白 则不扣积分
        if (!status.equals("Invalid")) {
            String uuid = taskId.substring(0, taskId.lastIndexOf("-", taskId.lastIndexOf("-") - 1));
            String accountId = taskId.substring(taskId.lastIndexOf("-") + 1);
            Boolean flag = creditsService.taskCreditsDeduction(Long.parseLong(accountId), uuid);
            if (flag) creditsService.updateChangedCredits(accountId, uuid);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processToProductImageResult(String taskId, String url, String category) {
        QueryWrapper<ToProductImageResult> qw = new QueryWrapper<>();
        qw.lambda().eq(ToProductImageResult::getTaskId, taskId);
        List<ToProductImageResult> toProductImageResults = toProductImageResultMapper.selectList(qw);
        if (CollectionUtils.isEmpty(toProductImageResults)) {
            return;
//            throw new BusinessException("");
        }
        ToProductImageResult toProductImageResult = toProductImageResults.get(0);
        toProductImageResult.setUrl(url);
        toProductImageResult.setStatus("Success");
//        toProductImageResult.setResultType("ToProductImage");
        toProductImageResultMapper.updateById(toProductImageResult);

        String key = toProductImageResultKey + ":" + taskId;
        String imageName = url.substring(url.lastIndexOf("/") + 1);
        String status = imageName.equals("white_image.jpg") ? "Invalid" : "Success";
        GenerateResultVO generateResultVO = new GenerateResultVO(taskId, toProductImageResult.getId(), url, status, category);
        redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

        Long accountId = Long.parseLong(taskId.substring(taskId.lastIndexOf("-") + 1));
        if (!status.equals("Invalid")) {
            // 4、扣除积分
            Boolean b = creditsService.taskCreditsDeduction(accountId, taskId);
            // 3、记录积分变更
            if (b) creditsService.insertToCreditsDetail(accountId,
                    TO_PRODUCT_IMAGE.getName(),
                    TO_PRODUCT_IMAGE.getValue(),
                    "negative", null);
        }
    }

    public void updateToProductTaskStatus(String taskId, String status) {
        QueryWrapper<ToProductImageResult> qw = new QueryWrapper<>();
        qw.lambda().eq(ToProductImageResult::getTaskId, taskId);
        List<ToProductImageResult> toProductImageResults = toProductImageResultMapper.selectList(qw);
        if (!CollectionUtils.isEmpty(toProductImageResults)) {
            ToProductImageResult toProductImageResult = toProductImageResults.get(0);
            if (StringUtil.isNullOrEmpty(toProductImageResult.getStatus()) || !toProductImageResult.getStatus().equals(status)) {
                toProductImageResult.setStatus(status);
                toProductImageResultMapper.updateById(toProductImageResult);
            }
        }
    }

    private void validateGeneraType(Generate generate, String text, Long elementId) {
        String generateType = "";
        if (StringUtil.isNullOrEmpty(text.trim()) && Objects.isNull(elementId)) {
            throw new BusinessException("please.input.the.caption.or.choose.an.image");
        } else if (!StringUtil.isNullOrEmpty(text.trim()) && !Objects.isNull(elementId)) {
            generateType = "text-image";
            generate.setText(text);
            generate.setElementId(elementId);
        } else if (!StringUtil.isNullOrEmpty(text.trim())) {
            generateType = "text";
            generate.setText(text);
        } else if (!Objects.isNull(elementId)) {
            generateType = "image";
            generate.setElementId(elementId);
        }
        generate.setGenerateType(generateType);

    }

    private String modifyPrompt(String userInput, Generate generate, String level1Type, String ageGroup) {
        String text = "";
        String prefix = "";
        if (userInput.startsWith("Painting Style")
                || userInput.startsWith("Illustration Style")
                || userInput.startsWith("Real Style")) {
            prefix = userInput.substring(0, userInput.indexOf(",")) + ", ";
            userInput = userInput.substring(userInput.indexOf(",") + 1);
        }
        // 替换用户输入中的中文字符
        log.info("用户输入，处理前：{}", userInput);
        userInput = ComprehensivePunctuationConverter.convertToHalfWidth(userInput);
        log.info("用户输入，处理后：{}", userInput);
        String translated = prefix + pythonService.promptTranslate(userInput);
        switch (level1Type) {
            case "Moodboard":
                text = translated + ",high quality";
//                generate.setText(text);
                break;
            case "Printboard":
            case "Pattern":
                text = translated;
                /*if (prefix.contains("Painting Style")) {
                    text = "Picasso,increased color saturation,increased glossiness," + translated + ", fabric print, high quality";
                } else if (prefix.contains("Illustration Style")) {
                    text = "Flat coating,romantic,soft,pencil strokes,accentuating and widening the depth of pencil strokes,paper patterns,block colors,crayons,reducing image contrast,and hand drawn painting marks," + translated + ", fabric print, high quality";
                } else if (prefix.contains("Real Style")) {
                    text = "Hyper realism,3d,deepened projection,increased permutation value,increased concavity and convexity value," + translated + ", fabric print, high quality";
                } else {
                    text = translated;
                }*/
//                text = userInput + ", fabric print, high quality";
//                generate.setText(text);
                break;
            case "Sketchboard":
//                text = "clear lines, simple outlines monochrome white vector image of " + translated + ", no background, sketch flat, front view display, best quality, ultra-high resolution 8k";
                text = "a single item of sketch of " + translated + ", 4k, white background";
                if (Objects.nonNull(generate) && generate.getModelName().equals("high")) {
                    text = text + ", only technical pattern of a single garment";
                }
                if (!StringUtil.isNullOrEmpty(ageGroup) && ageGroup.equals("Child")) {
                    text = text + ", Children's clothing";
                }
                break;
//                generate.setText(text);
            default:
                text = translated;
        }
        return text;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public GenerateLikeVO generateLike(GenerateLikeDTO generateLikeDTO) {
        // 1、判断参数是否正确
        // 1.1 必须参数是否非空
        if (SKETCH_BOARD.getRealName().equals(generateLikeDTO.getLevel1Type())) {
            if (StringUtil.isNullOrEmpty(generateLikeDTO.getLevel2Type())) {
                throw new BusinessException("level2Type.cannot.be.empty");
            }
            if (StringUtil.isNullOrEmpty(generateLikeDTO.getGender())) {
                throw new BusinessException("gender.cannot.be.empty");
            }
        }
        if (PRINT_BOARD.getRealName().equals(generateLikeDTO.getLevel1Type())) {
            if (StringUtil.isNullOrEmpty(generateLikeDTO.getLevel2Type())) {
                throw new BusinessException("level2Type.cannot.be.empty");
            }
        }
        // 1.2 判断参数是否真实有效
        Long generateDetailId = generateLikeDTO.getGenerateDetailId();
        GenerateDetail generateDetail = generateDetailMapper.selectById(generateDetailId);
        if (Objects.isNull(generateDetail)) {
            throw new BusinessException("generateItem.does.not.exist");
        }
        Generate generate = getById(generateDetail.getGenerateId());
        if (!generateLikeDTO.getLevel1Type().equals(generate.getLevel1Type())) {
            throw new BusinessException("level1Type.does.not.match");
        }

        // 2、将like的图片信息存入library
        // 2.1、不能重复喜欢
        // 2.1.1 判断该图片是否被喜欢过
        Library libraryDetail = libraryService.getById(generateDetail.getLibraryId());
        if ((Objects.nonNull(generateDetail.getLibraryId()) && !generateDetail.getLibraryId().equals(0L))
                || Objects.nonNull(libraryDetail)) {
            throw new BusinessException("duplicate.likes.are.not.allowed");
        }

        //  todo 2.1.2、判断library中是否有相同MD5的图片

        // 2.2、添加到library
        AuthPrincipalVo userInfo = UserContext.getUserHolder();
        Long accountId = userInfo.getId();
        Library library = setLibrary(accountId, generateLikeDTO, generateDetail.getUrl(), generateLikeDTO.getAgeGroup());
        libraryService.save(library);

        // 3、更新generateDetail表的isLike列和libraryId列
        updateLikeStatus(generateLikeDTO.getGenerateDetailId(), (byte) 1, library.getId(), generateLikeDTO.getTimeZone());

        return new GenerateLikeVO(library.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean generateDislike(Long generateDetailId, String timeZone) {
        // 1、确定generateDetail中是否有这条记录
        GenerateDetail generateDetail = generateDetailMapper.selectById(generateDetailId);
        if (Objects.isNull(generateDetail)) {
            throw new BusinessException("generateItem.does.not.exist");
        }

        // 2、修改generateDetail表中的isLike、libraryId字段
        updateLikeStatus(generateDetailId, (byte) 0, 0L, timeZone);

        // 3、确定library中是否添加该条记录
        Library libraryDetail = libraryService.getById(generateDetail.getLibraryId());
        if (Objects.isNull(libraryDetail)) {
            return Boolean.TRUE;
        }

        // 4、删除library相关记录
        libraryService.removeById(libraryDetail.getId());

        // 5、返回成功
        return Boolean.TRUE;
    }

    public Library setLibrary(Long accountId, GenerateLikeDTO generateLikeDTO, String imageUrl, String ageGroup) {
        Library library = new Library();
        library.setAccountId(accountId);
        library.setLevel1Type(generateLikeDTO.getLevel1Type());
        library.setLevel2Type(StringUtil.isNullOrEmpty(generateLikeDTO.getLevel2Type()) ? null : generateLikeDTO.getLevel2Type());
        library.setLevel3Type(StringUtil.isNullOrEmpty(generateLikeDTO.getGender()) ? null : generateLikeDTO.getGender());
        library.setAgeGroup(ageGroup);
        library.setName(DateUtil.getTimeStamp(generateLikeDTO.getTimeZone()) + "_N_G");
        library.setUrl(imageUrl);
        try {
            library.setMd5(MD5Utils.encryptFile(minioUtil.download(imageUrl)));
        } catch (MinioException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        library.setCreateDate(DateUtil.getByTimeZone(generateLikeDTO.getTimeZone()));
        return library;
    }

    public void updateLikeStatus(Long generateDetailId, Byte hasLike, Long libraryId, String timeZone) {
        QueryWrapper<GenerateDetail> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", generateDetailId);

        GenerateDetail generateDetail = new GenerateDetail();
        generateDetail.setIsLike(hasLike);
        generateDetail.setLibraryId(libraryId);
        generateDetail.setUpdateDate(DateUtil.getByTimeZone(timeZone));
        generateDetailMapper.update(generateDetail, queryWrapper);
    }

    // 避免循环注入，已移到libraryService中
    public void updateLikeStatusBatch(List<Long> generateDetailIdList, Byte hasLike, Long libraryId, String timeZone) {
        QueryWrapper<GenerateDetail> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("id", generateDetailIdList);

        GenerateDetail generateDetail = new GenerateDetail();
        generateDetail.setIsLike(hasLike);
        generateDetail.setLibraryId(libraryId);
        generateDetail.setUpdateDate(DateUtil.getByTimeZone(timeZone));

        generateDetailMapper.update(generateDetail, queryWrapper);
    }

    // 避免循环注入，已移到libraryService中
    public List<GenerateDetail> selectBatchByLibraryId(List<Long> libraryId) {
        QueryWrapper<GenerateDetail> qw = new QueryWrapper<>();
        qw.in("library_id", libraryId);

        return generateDetailMapper.selectList(qw);
    }

    @Override
    public PrepareForGenerateVO prepareForGenerate(GenerateThroughImageTextDTO generateDTO) {
        // 1、参数检查，判断必须参数是否为空
        validateRequiredParams(generateDTO);

        // 2、处理特殊模型情况（wx/flux）
        if (isWxModel(generateDTO)) {
            return handleWxModelGeneration(generateDTO);
        }
        if (isFluxPatternModel(generateDTO)) {
            return handleFluxPatternGeneration(generateDTO);
        }

        String modelName = generateDTO.getModelName();
        if (StringUtil.isNullOrEmpty(modelName)) {
            return handleStandardGeneration(generateDTO);
        }

        HashMap<String, String> modelAndPromptMap = chooseModelAndPrompt(generateDTO, modelName);
        String useModel = modelAndPromptMap.get(ModelConstants.USE_MODEL);
        String prompt = modelAndPromptMap.get(ModelConstants.PROMPT);

        if (useModel != null) {
            if (useModel.contains("doubao")) {
                return handleDouBaoModelGeneration(generateDTO, useModel, prompt);
            } else if (useModel.contains("qwen")) {
                return handleQwenModelGeneration(generateDTO, useModel, prompt);
            } else if (useModel.contains("imagen")) {
                return handleImagenModelGeneration(generateDTO, useModel, prompt);
            } else if (useModel.contains("gemini")) {
                return handleNanoBananaModelGeneration(generateDTO, useModel, prompt);
            } else if (ModelConstants.LOCAL_MODEL.equals(useModel)) {
                return handleStandardGeneration(generateDTO);
            }
        }

        return handleStandardGeneration(generateDTO);

    }


    // ============== 以下是辅助方法 ==============
    private PrepareForGenerateVO handleImagenModelGeneration(GenerateThroughImageTextDTO generateDTO, String useModel, String prompt) {
        // 校验积分是否足够
        validateCredits(CreditsEventsEnum.SCETCH_TEXT2IMG);
        // 创建生成任务
        String taskId = null;
        try {
            taskId = createGoogleAsyncTask(generateDTO, useModel, prompt);
        } catch (Exception e) {
            e.printStackTrace();
        }
        processCreditDeduction(generateDTO.getUserId(), taskId, CreditsEventsEnum.SCETCH_TEXT2IMG);
        return new PrepareForGenerateVO(Collections.singletonList(taskId), 200);
    }

    private PrepareForGenerateVO handleNanoBananaModelGeneration(GenerateThroughImageTextDTO generateDTO, String useModel, String prompt) {
        // 校验积分是否足够
        validateCredits(CreditsEventsEnum.SCETCH_IMG2IMG);
        // 创建生成任务
        String taskId = null;
        try {
            taskId = createGoogleAsyncTask(generateDTO, useModel, prompt);
        } catch (Exception e) {
            e.printStackTrace();
        }
        processCreditDeduction(generateDTO.getUserId(), taskId, CreditsEventsEnum.SCETCH_IMG2IMG);
        return new PrepareForGenerateVO(Collections.singletonList(taskId), 200);
    }

    public String toProductAsyncTask(String imagePath, String useModel, String prompt) {

        long startTime = System.currentTimeMillis();
        log.info("开始执行toProductAsyncTask - model: {}, imagePath: {}, prompt: {}",
                useModel, imagePath, prompt);

        if (StringUtil.isNullOrEmpty(imagePath) || StringUtil.isNullOrEmpty(useModel) || StringUtil.isNullOrEmpty(prompt)) {
            log.error("参数验证失败 - imagePath: {}, useModel: {}, prompt: {}", imagePath, useModel, prompt);
            throw new BusinessException("Parameter Exception");
        }
        AuthPrincipalVo userHolder = UserContext.getUserHolder();
        Long userId = userHolder.getId();

        String uuid = UUID.randomUUID().toString();
        // 生成唯一的任务ID
        String taskId = uuid + "-" + userId;
        String finalImagePath = null;
        try {
            finalImagePath = addWhiteBackground(imagePath);
            // 如果白色背景处理成功，对base64图片进行压缩以降低模型成本
            if (!StringUtil.isNullOrEmpty(finalImagePath)) {
                // 压缩到512x512分辨率
                finalImagePath = minioUtil.compressBase64Image(finalImagePath, 512, 512);
                //去掉"data:image/png;base64,"
                finalImagePath = finalImagePath.replace("data:image/png;base64,", "");
            } else {
                // 如果白色背景处理失败或不需要，直接获取原图的压缩base64编码
                finalImagePath = minioUtil.getCompressedImageAsBase64(imagePath, 512, 512);
            }
        } catch (IOException e) {
            log.error("Error getting image as base64 taskId: {} ", taskId, e);
            throw new BusinessException("Parameter Exception");
        }

        // 初始化Redis中的任务状态为"Executing"
        String key = generateResultKey + ":" + taskId;
        // 异步处理token获取和API调用
        String projectId = "aida-461108";
        String location = "global";
        String endpoint = String.format(
                "https://aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:generateContent",
                projectId, location, ModelConstants.NANO_BANANA
        );

        JSONObject requestBody = new JSONObject();
        // 使用 gemini-2.5-flash-image-preview 模型时的请求体格式
        // 创建图片部分
        JSONObject imagePart = new JSONObject();
        JSONObject inlineData = new JSONObject();
        inlineData.set("mimeType", "image/png");
        inlineData.set("data", finalImagePath);
        imagePart.set("inlineData", inlineData);

        // 创建文本部分
        JSONObject textPart = new JSONObject();
        textPart.set("text", prompt);

        // 创建内容对象
        JSONObject content = new JSONObject();
        content.set("role", "user");
        content.set("parts", Arrays.asList(imagePart, textPart));

        // 设置 contents 数组
        requestBody.set("contents", Arrays.asList(content));

        // 设置 generationConfig
        JSONObject generationConfig = new JSONObject();
//            generationConfig.set("temperature", 1);
        generationConfig.set("maxOutputTokens", 8192);
        generationConfig.set("responseModalities", Arrays.asList("IMAGE"));
//            generationConfig.set("topP", 0.95);
        JSONObject imageConfig = new JSONObject();
        imageConfig.set("aspectRatio", "9:16");
        generationConfig.set("imageConfig", imageConfig);
        requestBody.set("generationConfig", generationConfig);
        String jsonBody = requestBody.toString();
//        log.info("Google 请求入参：{}", jsonBody);

        GenerateResultVO resultVO = new GenerateResultVO(taskId, null, null, "Pending");
        redisUtil.addToString(key, new Gson().toJson(resultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
        CompletableFuture.runAsync(() -> {
            try {
                // 异步获取token
                String tokenValue = null;
                try (InputStream inputStream = GenerateServiceImpl.class.getClassLoader()
                        .getResourceAsStream("aida-461108-b4afaabebb84.json")) {

                    GoogleCredentials credentials = GoogleCredentials
                            .fromStream(inputStream)
                            .createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"));

                    credentials.refreshIfExpired();
                    tokenValue = credentials.getAccessToken().getTokenValue();
                }
                if (tokenValue == null) {
                    throw new RuntimeException("google token error");
                }

                // 异步发送API请求 - 优化网络配置以解决连接中断问题
                ConnectionPool connectionPool = new ConnectionPool(10, 5, TimeUnit.MINUTES);
                OkHttpClient client = new OkHttpClient.Builder()
                        .connectTimeout(45, TimeUnit.SECONDS)    // 增加连接超时时间
                        .readTimeout(120, TimeUnit.SECONDS)      // 增加读取超时时间
                        .writeTimeout(120, TimeUnit.SECONDS)     // 增加写入超时时间
                        .callTimeout(180, TimeUnit.SECONDS)      // 添加总调用超时时间
                        .connectionPool(connectionPool)          // 使用连接池
                        .retryOnConnectionFailure(true)          // 启用连接失败重试
                        .build();
                Request request = new Request.Builder()
                        .url(endpoint)
                        // 移除Connection: close头，使用Keep-Alive连接
                        .addHeader("Authorization", "Bearer " + tokenValue)
                        .addHeader("Content-Type", "application/json")
                        .addHeader("User-Agent", "AIDA-Client/1.0")
                        .addHeader("Accept", "application/json")
                        .post(RequestBody.create(MediaType.parse("application/json"), jsonBody))
                        .build();

                // 实现重试逻辑来处理网络连接问题
                int maxRetries = 3;
                int retryDelay = 2000; // 2秒
                boolean success = false;

                for (int attempt = 1; attempt <= maxRetries && !success; attempt++) {
                    try {
                        log.info("发起HTTP请求 - 尝试次数: {}/{}, URL: {}, taskId: {}", attempt, maxRetries, request.url(), taskId);
                        long requestStartTime = System.currentTimeMillis();

                        try (Response response = client.newCall(request).execute()) {
                            long requestEndTime = System.currentTimeMillis();
                            log.info("HTTP请求完成 - 响应状态: {}, 耗时: {}ms, taskId: {}",
                                    response.code(), (requestEndTime - requestStartTime), taskId);
                            if (!response.isSuccessful()) {
                                log.warn("Google API响应失败，状态码: {} for taskId: {}", response.code(), taskId);
                                if (attempt < maxRetries) {
                                    Thread.sleep(retryDelay * attempt); // 递增延迟
                                    continue;
                                } else {
                                    throw new IOException("HTTP error code: " + response.code());
                                }
                            }

                            String result = response.body().string();
//                            log.info("Google 响应结果：{}", result);
                            com.alibaba.fastjson.JSONObject jsonResponse = JSON.parseObject(result);

                            String base64Data = null;

                            //根据模型类别按照api取出结果
                            if (ModelConstants.NANO_BANANA.equals(useModel)) {
                                JSONArray candidates = jsonResponse.getJSONArray("candidates");

                                if (candidates != null && !candidates.isEmpty()) {
                                    com.alibaba.fastjson.JSONObject candidate = candidates.getJSONObject(0);
                                    String finishReason = candidate.getString("finishReason");
                                    if (finishReason != null && finishReason.equals("STOP")) {
                                        success = true;
                                        com.alibaba.fastjson.JSONObject contentResult = candidate.getJSONObject("content");
                                        JSONArray parts = contentResult.getJSONArray("parts");

                                        // 遍历parts数组找到包含inlineData的对象
                                        for (int i = 0; i < parts.size(); i++) {
                                            com.alibaba.fastjson.JSONObject part = parts.getJSONObject(i);
                                            if (part.containsKey("inlineData")) {
                                                com.alibaba.fastjson.JSONObject inlineDataResult = part.getJSONObject("inlineData");
                                                base64Data = inlineDataResult.getString("data");
                                                break;
                                            }
                                        }
                                    } else if (finishReason != null && finishReason.equals("IMAGE_SAFETY")) {
                                        String finishMessage = candidate.getString("finishMessage");
                                        if (finishMessage != null && finishMessage.contains("Try rephrasing the prompt")) {
                                            finishMessage = "Try rephrasing the prompt or modifying the model image. If you think this was an error, send feedback.";
                                            LambdaQueryWrapper<Account> select = new LambdaQueryWrapper<Account>().eq(Account::getId, userId).select(Account::getLanguage);
                                            Account account = accountService.getOne(select);
                                            if ("CHINESE_SIMPLIFIED".equals(account.getLanguage())) {
                                                finishMessage = "请尝试重新表述提示词，或修改模特图。若您认为这是误判，可提交反馈。";
                                            }
                                            GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, finishMessage, "Fail");
                                            redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
                                            return;
                                        }
                                    }

                                }
                            }

                            if (base64Data != null && !base64Data.isEmpty()) {
                                String resultPath = userId + "/product_image" + "/" + uuid;
                                String minioPath = minioUtil.base64UploadToPath("data:image/png;base64," + base64Data, userBucket, resultPath);
                                if (StringUtil.isNullOrEmpty(minioPath)) {
                                    log.warn("Google API调用成功，但图片保存失败 for taskId: {}", taskId);
                                }
                                // 生成成功，更新Redis状态和URL
                                GenerateResultVO successResultVO = new GenerateResultVO(taskId, null, minioPath, "Success");
                                redisUtil.addToString(key, new Gson().toJson(successResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
                                success = true;
                                log.info("Google API调用成功 for taskId: {}", taskId);
                            } else {
                                // 没有找到图像数据或数据为空，标记为失败
                                log.warn("Google generation response does not contain valid image data for taskId: {}", taskId);
                                GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, null, "Fail");
                                redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
                                success = true; // 这是业务逻辑失败，不需要重试
                            }
                        }
                    } catch (IOException e) {
                        log.warn("网络连接问题 - 异常类型: {}, 尝试: {}/{}, taskId: {}, 错误详情: {}",
                                e.getClass().getSimpleName(), attempt, maxRetries, taskId, e.getMessage());
                        if (attempt < maxRetries) {
                            int delayMs = retryDelay * attempt;
                            log.info("准备重试 - 延迟: {}ms, 下次尝试: {}/{}, taskId: {}",
                                    delayMs, (attempt + 1), maxRetries, taskId);
                            try {
                                Thread.sleep(delayMs); // 递增延迟重试
                            } catch (InterruptedException ie) {
                                Thread.currentThread().interrupt();
                                log.error("重试被中断 - taskId: {}", taskId, ie);
                                throw new RuntimeException("重试被中断", ie);
                            }
                        } else {
                            log.error("Google API调用最终失败 - 已达到最大重试次数: {}, taskId: {}", maxRetries, taskId, e);
                            throw e; // 最后一次尝试失败，抛出异常
                        }
                    }
                }
            } catch (Exception e) {
                log.error("Google generation failed for taskId: {}", taskId, e);
                // 生成失败，更新Redis状态
                GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, null, "Fail");
                redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
            }
        }, asyncTaskExecutor);

        long endTime = System.currentTimeMillis();
        log.info("toProductAsyncTask执行完成 - taskId: {}, 总耗时: {}ms", taskId, (endTime - startTime));
        return taskId;
    }


    public String createGoogleAsyncTask(GenerateThroughImageTextDTO generateDTO, String useModel, String prompt) {
        // 从 resources 加载 JSON 文件
        String level1Type = generateDTO.getLevel1Type();
        String level2Type = generateDTO.getLevel2Type();
        Long userId = generateDTO.getUserId();
        String gender = generateDTO.getGender();


        boolean isUseImage;
        String resultPath;
        String uuid = UUID.randomUUID().toString();
        if (Objects.isNull(generateDTO.getCollectionElementId())
                || StringUtil.isNullOrEmpty(generateDTO.getDesignType())) {
            isUseImage = false;
        } else {
            isUseImage = true;
        }
        String model;
        String finalImagePath = null;
        if (isUseImage) {
            String imagePath = geti2iImagePath(generateDTO);

            // 只有当imagePath不为null时才获取图片的base64编码
            if (imagePath != null) {
                try {
                    finalImagePath = minioUtil.getImageAsBase64(imagePath);
                } catch (IOException e) {
                    log.error("获取图片base64编码失败", e);
                    e.printStackTrace();
                }
            }
            resultPath = userId + "/imageToSketch" + "/" + uuid;
            model = ModelConstants.NANO_BANANA;
        } else {
            resultPath = userId + "/sketch" + "/" + uuid;
            model = ModelConstants.IMAGEN_MODEL;
        }
        // 拼接 endpoint
        String projectId = "aida-461108";
        String location;
        String endpoint;


        JSONObject instanceObj = new JSONObject();
        JSONObject parametersObj = new JSONObject();
        JSONObject requestBody = new JSONObject();

        if (isUseImage && finalImagePath != null) {
            location = "global";
            endpoint = String.format(
                    "https://aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:generateContent",
                    projectId, location, model
            );

            // 使用 gemini-2.5-flash-image-preview 模型时的请求体格式
            // 创建图片部分
            JSONObject imagePart = new JSONObject();
            JSONObject inlineData = new JSONObject();
            inlineData.set("mimeType", "image/png");
            inlineData.set("data", finalImagePath);
            imagePart.set("inlineData", inlineData);

            // 创建文本部分
            JSONObject textPart = new JSONObject();
            textPart.set("text", prompt);

            // 创建内容对象
            JSONObject content = new JSONObject();
            content.set("role", "user");
            content.set("parts", Arrays.asList(imagePart, textPart));

            // 设置 contents 数组
            requestBody.set("contents", Arrays.asList(content));

            // 设置 generationConfig
            JSONObject generationConfig = new JSONObject();
//            generationConfig.set("temperature", 1);
            generationConfig.set("maxOutputTokens", 8192);
            generationConfig.set("responseModalities", Arrays.asList("IMAGE"));
            JSONObject imageConfig = new JSONObject();
            imageConfig.set("aspectRatio", "9:16");
            generationConfig.set("imageConfig", imageConfig);
//            generationConfig.set("topP", 0.95);
            requestBody.set("generationConfig", generationConfig);


        } else {
            location = "us-central1";
            endpoint = String.format(
                    "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:predict",
                    location, projectId, location, model
            );
            // 使用 imagen 模型时的请求体格式
            instanceObj.set("prompt", prompt);

            // imagen 模型的参数
            parametersObj.set("addWatermark", false);
            parametersObj.set("sampleCount", 1);
            parametersObj.set("aspectRatio", "9:16");

            // 构建完整的请求体
            requestBody.set("instances", Arrays.asList(instanceObj));
            requestBody.set("parameters", parametersObj);
        }

        String jsonBody = requestBody.toString();

//        log.info("Google 请求入参：{}", jsonBody);

        // 生成唯一的任务ID
        String taskId = uuid + "-" + userId;

        // 初始化Redis中的任务状态为"Executing"
        String key = generateResultKey + ":" + taskId;
        GenerateResultVO resultVO = new GenerateResultVO(taskId, null, null, "Executing");
        redisUtil.addToString(key, new Gson().toJson(resultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

        // 异步处理token获取和API调用
        CompletableFuture.runAsync(() -> {
            long startTime = System.currentTimeMillis();
            log.info("开始执行 createGoogleAsyncTask 方法，taskId: {}, model: {}, prompt: {}", taskId, useModel, prompt);

            final int maxRetries = 3;
            final int retryDelayMs = 2000;

            for (int attempt = 1; attempt <= maxRetries; attempt++) {
                try {
                    log.info("Google API 调用尝试 {}/{} - taskId: {}", attempt, maxRetries, taskId);

                    // 异步获取token
                    String tokenValue = null;
                    try (InputStream inputStream = GenerateServiceImpl.class.getClassLoader()
                            .getResourceAsStream("aida-461108-b4afaabebb84.json")) {

                        GoogleCredentials credentials = GoogleCredentials
                                .fromStream(inputStream)
                                .createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"));

                        credentials.refreshIfExpired();
                        tokenValue = credentials.getAccessToken().getTokenValue();
                    }
                    if (tokenValue == null) {
                        throw new RuntimeException("google token error");
                    }

                    // 异步发送API请求
                    OkHttpClient client = new OkHttpClient.Builder()
                            .connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
                            .connectTimeout(45, TimeUnit.SECONDS)
                            .readTimeout(120, TimeUnit.SECONDS)
                            .writeTimeout(120, TimeUnit.SECONDS)
                            .callTimeout(180, TimeUnit.SECONDS)
                            .retryOnConnectionFailure(true)
                            .build();

                    Request request = new Request.Builder()
                            .url(endpoint)
                            .addHeader("Authorization", "Bearer " + tokenValue)
                            .addHeader("Content-Type", "application/json")
                            .addHeader("User-Agent", "AIDA-Backend/1.0")
                            .addHeader("Accept", "application/json")
                            .post(RequestBody.create(MediaType.parse("application/json"), jsonBody))
                            .build();

                    long requestStartTime = System.currentTimeMillis();
                    try (Response response = client.newCall(request).execute()) {
                        long requestDuration = System.currentTimeMillis() - requestStartTime;
                        log.info("Google API 请求完成 - taskId: {}, 尝试: {}, URL: {}, 状态码: {}, 耗时: {}ms",
                                taskId, attempt, endpoint, response.code(), requestDuration);

                        String result = response.body().string();

//                        log.info("Google 响应结果：{}", result);
                        com.alibaba.fastjson.JSONObject jsonResponse = JSON.parseObject(result);

                        String base64Data = null;

                        //根据模型类别按照api取出结果
                        if (ModelConstants.NANO_BANANA.equals(useModel)) {
                            JSONArray candidates = jsonResponse.getJSONArray("candidates");

                            if (candidates != null && !candidates.isEmpty()) {
                                com.alibaba.fastjson.JSONObject candidate = candidates.getJSONObject(0);
                                com.alibaba.fastjson.JSONObject content = candidate.getJSONObject("content");
                                String finishReason = candidate.getString("finishReason");
                                if (finishReason != null && finishReason.equals("STOP")) {
                                    JSONArray parts = content.getJSONArray("parts");

                                    // 遍历parts数组找到包含inlineData的对象
                                    for (int i = 0; i < parts.size(); i++) {
                                        com.alibaba.fastjson.JSONObject part = parts.getJSONObject(i);
                                        if (part.containsKey("inlineData")) {
                                            com.alibaba.fastjson.JSONObject inlineData = part.getJSONObject("inlineData");
                                            base64Data = inlineData.getString("data");
                                            break;
                                        }
                                    }
                                } else if (finishReason != null && finishReason.equals("IMAGE_SAFETY")) {
                                    String finishMessage = candidate.getString("finishMessage");
                                    if (finishMessage != null && finishMessage.contains("Try rephrasing the prompt")) {
                                        finishMessage = "Try rephrasing the prompt or replacing the image. If you think this was an error, send feedback.";
                                        LambdaQueryWrapper<Account> select = new LambdaQueryWrapper<Account>().eq(Account::getId, userId).select(Account::getLanguage);
                                        Account account = accountService.getOne(select);
                                        if ("CHINESE_SIMPLIFIED".equals(account.getLanguage())) {
                                            finishMessage = "请尝试重新表述提示词，或更换图片。若您认为这是误判，可提交反馈。";
                                        }
                                        GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, finishMessage, "Fail");
                                        redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
                                    }
                                    return;
                                }

                            }
                        } else if (ModelConstants.IMAGEN_MODEL.equals(useModel)) {
                            JSONArray predictions = jsonResponse.getJSONArray("predictions");

                            if (predictions != null && !predictions.isEmpty()) {
                                com.alibaba.fastjson.JSONObject prediction = predictions.getJSONObject(0);
                                base64Data = prediction.getString("bytesBase64Encoded");
                                String raiFilteredReason = prediction.getString("raiFilteredReason");
                                if (raiFilteredReason != null && raiFilteredReason.contains("Try rephrasing the prompt")) {
                                    raiFilteredReason = "Input data may contain inappropriate content.";
                                    LambdaQueryWrapper<Account> select = new LambdaQueryWrapper<Account>().eq(Account::getId, userId).select(Account::getLanguage);
                                    Account account = accountService.getOne(select);
                                    if ("CHINESE_SIMPLIFIED".equals(account.getLanguage())) {
                                        raiFilteredReason = "输入数据可能包含不当内容。";
                                    }
                                    GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, raiFilteredReason, "Fail");
                                    redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
                                }
                            }
                        }

                        if (base64Data != null && !base64Data.isEmpty()) {
                            String minioPath = minioUtil.base64UploadToPath("data:image/png;base64," + base64Data, userBucket, resultPath);
                            // 生成成功，更新Redis状态和URL
                            GenerateResultVO successResultVO = new GenerateResultVO(taskId, null, minioPath, "Success");
                            redisUtil.addToString(key, new Gson().toJson(successResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

                            long totalDuration = System.currentTimeMillis() - startTime;
                            log.info("Google 图片生成成功 - taskId: {}, 总耗时: {}ms", taskId, totalDuration);
                            return; // 成功后立即返回
                        } else {
                            // 没有找到图像数据或数据为空，标记为失败（业务逻辑失败，不重试）
                            log.warn("Google generation response does not contain valid image data for taskId: {}", taskId);
                            GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, null, "Fail");
                            redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
                            return; // 业务逻辑失败，直接返回不重试
                        }
                    }
                } catch (IOException e) {
                    // 只对 IOException 进行重试
                    log.warn("Google API 调用发生 IOException - taskId: {}, 尝试: {}/{}, 异常: {}",
                            taskId, attempt, maxRetries, e.getMessage());

                    if (attempt < maxRetries) {
                        try {
                            long delay = retryDelayMs * attempt; // 递增延迟
                            log.info("等待 {}ms 后进行第 {} 次重试 - taskId: {}", delay, attempt + 1, taskId);
                            Thread.sleep(delay);
                        } catch (InterruptedException ie) {
                            Thread.currentThread().interrupt();
                            log.error("重试等待被中断 - taskId: {}", taskId);
                            break;
                        }
                    } else {
                        // 达到最大重试次数，记录错误并更新Redis状态
                        log.error("Google API 调用最终失败，已达到最大重试次数 {} - taskId: {}", maxRetries, taskId, e);
                        GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, null, "Fail");
                        redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
                    }
                } catch (Exception e) {
                    // 对于其他异常（非IOException），记录错误并直接返回，不重试
                    log.error("Google API 调用发生意外异常 - taskId: {}, 异常类型: {}, 异常信息: {}",
                            taskId, e.getClass().getSimpleName(), e.getMessage(), e);
                    GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, null, "Fail");
                    redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
                    return; // 非网络异常，直接返回不重试
                }
            }

            // 如果所有重试都失败了，记录最终失败日志
            long totalDuration = System.currentTimeMillis() - startTime;
            log.error("Google API 调用最终失败，所有重试都已用尽 - taskId: {}, 总耗时: {}ms", taskId, totalDuration);
            GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, null, "Fail");
            redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
        }, asyncTaskExecutor);

        // 保存生成记录到数据库
        Generate generate = new Generate(
                userId,
                taskId,
                level1Type,
                level2Type,
                generateDTO.getText(),
                "text(" + gender + ")",
                useModel,  // 记录使用的具体模型名称
                new Date()
        );
        save(generate);


        return taskId;
    }

    /**
     * @param generateDTO
     * @param modelName   advanced  high  normal
     */
    private HashMap<String, String> chooseModelAndPrompt(GenerateThroughImageTextDTO generateDTO, String modelName) {
        HashMap<String, String> modelAndPromptMap = new HashMap<>();
        boolean isUseImage;
        if (Objects.isNull(generateDTO.getCollectionElementId())
                || StringUtil.isNullOrEmpty(generateDTO.getDesignType())) {
            isUseImage = false;
        } else {
            isUseImage = true;
        }
        if (ModelConstants.PRINTBOARD.equals(generateDTO.getLevel1Type())) {

            int firstCommaIndex = generateDTO.getText().indexOf(",");

            String style = generateDTO.getText().substring(0, firstCommaIndex).trim();

            String prompt = generateDTO.getText().substring(firstCommaIndex + 1).trim();
            prompt = getPrintboardPrompt(style, prompt);
            modelAndPromptMap.put(ModelConstants.PROMPT, prompt);


            if (ModelConstants.ADVANCED.equals(modelName)) {
                if (isUseImage) {
                    modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.PRINTBOARD_ADVANCED_I2I);// i2i
                } else {
                    modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.PRINTBOARD_ADVANCED_T2I);// t2i
                }
            } else if (ModelConstants.HIGH.equals(modelName)) {
                if (isUseImage) {
                    modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.PRINTBOARD_HIGH_I2I);// i2i
                } else {
                    modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.PRINTBOARD_HIGH_T2I);// t2i
                }
            } else if (ModelConstants.NORMAL.equals(modelName)) {
                modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.LOCAL_MODEL);
            }

        } else if (ModelConstants.MOODBOARD.equals(generateDTO.getLevel1Type())) {
            String prompt = generateDTO.getText() + "high-resolution, ultra-detailed, realistic textures, perfect anatomy, cinematic lighting, 8k render, editorial photography style";
            modelAndPromptMap.put(ModelConstants.PROMPT, prompt);

            if (ModelConstants.ADVANCED.equals(modelName)) {
                modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.MOODBOARD_ADVANCED);
            } else if (ModelConstants.HIGH.equals(modelName)) {
                modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.LOCAL_MODEL);
            } else if (ModelConstants.NORMAL.equals(modelName)) {
                modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.LOCAL_MODEL);
            }
        } else if (ModelConstants.SKETCHBOARD.equals(generateDTO.getLevel1Type())) {
            String prompt = generateDTO.getText() + "rules:front view sketch only,plain white background, single garment only, orthographic, centered on white background, borderless canvas, thin monochrome black line art.\n" +
                    " No clothes hanger, no fake clothes hanger, no human-related lines, no color fill, no words, no text, no black background, no boundary or frame.";
            modelAndPromptMap.put(ModelConstants.PROMPT, prompt);
            if (isUseImage) {
                if (ModelConstants.ADVANCED.equals(modelName)) {
                    modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.NANO_BANANA);
                } else if (ModelConstants.HIGH.equals(modelName)) {
                    modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.NANO_BANANA);
                } else if (ModelConstants.NORMAL.equals(modelName)) {
                    modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.LOCAL_MODEL);
                }
            } else {
                if (ModelConstants.ADVANCED.equals(modelName)) {
                    modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.IMAGEN_MODEL);
                } else if (ModelConstants.HIGH.equals(modelName)) {
                    modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.IMAGEN_MODEL);
                } else if (ModelConstants.NORMAL.equals(modelName)) {
                    modelAndPromptMap.put(ModelConstants.USE_MODEL, ModelConstants.LOCAL_MODEL);
                }
            }

        }
        return modelAndPromptMap;
    }

    /**
     * 参数校验
     */
    private void validateRequiredParams(GenerateThroughImageTextDTO generateDTO) {
        if (Objects.isNull(generateDTO.getUserId())) {
            throw new BusinessException("userId.cannot.be.empty");
        }

        // Printboard必须要有level2Type
        if (generateDTO.getLevel1Type().equals(PRINT_BOARD.getRealName())
                && StringUtil.isNullOrEmpty(generateDTO.getLevel2Type())) {
            throw new BusinessException("level2Type.cannot.be.empty");
        }
        if (generateDTO.getLevel1Type().equals(PRINT_BOARD.getRealName()) && generateDTO.getLevel2Type().equals(CreditsEventsEnum.PATTERN.getName())) {
            int firstCommaIndex = generateDTO.getText().indexOf(",");
            String style = generateDTO.getText().substring(0, firstCommaIndex).trim();
            //如果style不等于painting style，illustration style，real style中的一种
            if (!style.equals(ModelConstants.PAINTING_STYLE) && !style.equals(ModelConstants.ILLUSTRATION_STYLE) && !style.equals(ModelConstants.REAL_STYLE)) {
                throw new BusinessException("style Parameter Exception");
            }
        }

    }

    /**
     * 判断是否为wx模型
     */
    private boolean isWxModel(GenerateThroughImageTextDTO generateDTO) {
        return !StringUtil.isNullOrEmpty(generateDTO.getModelName())
                && "wx".equals(generateDTO.getModelName());
    }

    /**
     * 处理wx模型生成
     */
    private PrepareForGenerateVO handleWxModelGeneration(GenerateThroughImageTextDTO generateDTO) {
        // 校验积分是否足够
        validateCredits(CreditsEventsEnum.WX_TEXT2IMG);
        // 创建生成任务
        String taskId = createAsyncTask(generateDTO);
        processCreditDeduction(generateDTO.getUserId(), taskId, CreditsEventsEnum.WX_TEXT2IMG);
        return new PrepareForGenerateVO(Collections.singletonList(taskId), 200);
    }

    private PrepareForGenerateVO handleQwenModelGeneration(GenerateThroughImageTextDTO generateDTO, String useModel, String prompt) {

        validateCredits(CreditsEventsEnum.QWEN_TEXT2IMG);
        String taskId = createQwenAsyncTask(generateDTO, useModel, prompt);
        processCreditDeduction(generateDTO.getUserId(), taskId, CreditsEventsEnum.QWEN_TEXT2IMG);
        return new PrepareForGenerateVO(Collections.singletonList(taskId), 200);
    }

    private PrepareForGenerateVO handleDouBaoModelGeneration(GenerateThroughImageTextDTO generateDTO, String useModel, String prompt) {
        CreditsEventsEnum creditsEvents = null;
        if (useModel.equals(ModelConstants.PRINTBOARD_ADVANCED_I2I)) {
            creditsEvents = CreditsEventsEnum.DOUBAO_IMG2IMG_ADVANCED;
        } else if (useModel.equals(ModelConstants.PRINTBOARD_HIGH_I2I)) {
            creditsEvents = CreditsEventsEnum.DOUBAO_IMG2IMG_HIGH;
        } else if (useModel.equals(ModelConstants.PRINTBOARD_HIGH_T2I) || useModel.equals(ModelConstants.MOODBOARD_ADVANCED)) {
            creditsEvents = CreditsEventsEnum.DOUBAO_TEXT2IMG;
        }
        validateCredits(creditsEvents);
        String taskId = createDouBaoAsyncTask(generateDTO, useModel, prompt);
        processCreditDeduction(generateDTO.getUserId(), taskId, creditsEvents);

        return new PrepareForGenerateVO(Collections.singletonList(taskId), 200);
    }

    public String createQwenAsyncTask(GenerateThroughImageTextDTO generateDTO, String useModel, String prompt) {

        String level1Type = generateDTO.getLevel1Type();
        String level2Type = generateDTO.getLevel2Type();

        Long userId = generateDTO.getUserId();
        String gender = generateDTO.getGender();


        Map<String, Object> parameters = new HashMap<>();
        parameters.put("prompt_extend", false);
        parameters.put("watermark", false);
        ImageSynthesisParam param =
                ImageSynthesisParam.builder()
                        .apiKey(ALIYUN_API_KEY)
                        .model(useModel)
                        .prompt(prompt)
                        .n(1)
                        .size("1328*1328")
                        .parameters(parameters)
                        .build();
        log.info(param.toString());

        ImageSynthesis imageSynthesis = new ImageSynthesis();
        ImageSynthesisResult result = null;
        try {
            result = imageSynthesis.asyncCall(param);
        } catch (Exception e) {

            String exceptionMessage = e.getMessage();
            if (exceptionMessage != null && exceptionMessage.contains("\"message\"")) {
                try {
                    // 解析JSON格式的异常信息
                    JSONObject jsonObj = JSONUtil.parseObj(exceptionMessage);
                    exceptionMessage = jsonObj.getStr("message");
                    if ("Input data may contain inappropriate content.".equals(exceptionMessage)) {
                        LambdaQueryWrapper<Account> select = new LambdaQueryWrapper<Account>().eq(Account::getId, userId).select(Account::getLanguage);
                        Account account = accountService.getOne(select);
                        if ("CHINESE_SIMPLIFIED".equals(account.getLanguage())) {
                            exceptionMessage = "输入数据可能包含不当内容。";
                        }
                    }
                    throw new BusinessException(exceptionMessage, ResultEnum.PROMPT.getCode());
                } catch (Exception parseException) {
                    // 如果解析失败，返回原始消息
                    throw new BusinessException(exceptionMessage, ResultEnum.PROMPT.getCode());
                }
            }
            throw new BusinessException(e.getMessage(), ResultEnum.PROMPT.getCode());
        }
        String taskId = result.getOutput().getTaskId();
        log.info("qwen text2image 请求生成:{}, taskId：{}", JsonUtils.toJson(result), taskId);

        Generate generate = new Generate(userId, taskId, level1Type, level2Type, generateDTO.getText(), "text(" + gender + ")", "qwen-image", new Date());
        save(generate);
        return taskId;
    }


    public String createDouBaoAsyncTask(GenerateThroughImageTextDTO generateDTO, String useModel, String prompt) {
        // 从DTO中获取基础参数
        String level1Type = generateDTO.getLevel1Type();
        String level2Type = generateDTO.getLevel2Type();
        Long userId = generateDTO.getUserId();
        String gender = generateDTO.getGender();
        // 获取图片路径
        String imagePath = geti2iImagePath(generateDTO);
        String finalImagePath = null;

        // 只有当imagePath不为null时才获取图片的base64编码
        if (imagePath != null) {
            try {
                String imageAsBase64 = minioUtil.getImageAsBase64(imagePath);
                finalImagePath = "data:image/png;base64," + imageAsBase64;
            } catch (IOException e) {
                log.error("获取图片base64编码失败", e);
                e.printStackTrace();
            }
        }

        // 生成唯一的任务ID
        String taskId = UUID.randomUUID().toString() + "-" + userId;

        // 初始化Redis中的任务状态为"Executing"
        String key = generateResultKey + ":" + taskId;
        GenerateResultVO resultVO = new GenerateResultVO(taskId, null, null, "Executing");
        redisUtil.addToString(key, new Gson().toJson(resultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

        String finalImagePath1 = finalImagePath;
        // 异步执行图像生成任务
        CompletableFuture.runAsync(() -> {
            try {
                ConnectionPool connectionPool = new ConnectionPool(5, 1, TimeUnit.SECONDS);
                Dispatcher dispatcher = new Dispatcher();
                ArkService service = ArkService.builder().dispatcher(dispatcher).connectionPool(connectionPool).apiKey(DOUBAO_API_KEY).build();

                // 根据是否有图片路径来构建请求
                GenerateImagesRequest.Builder requestBuilder = GenerateImagesRequest.builder()
                        .watermark(false)
                        .model(useModel)
                        .prompt(prompt);

                // 只有当finalImagePath不为null时才设置image参数
                if (imagePath != null) {
                    requestBuilder.image(finalImagePath1);
                }

                // 保存生成记录到数据库
                Generate generate = new Generate(
                        userId,
                        taskId,
                        level1Type,
                        level2Type,
                        generateDTO.getText(),
                        "text(" + gender + ")",
                        useModel,  // 记录使用的具体模型名称
                        new Date()
                );
                save(generate);

                GenerateImagesRequest generateRequest = requestBuilder.build();

                ImagesResponse imagesResponse = service.generateImages(generateRequest);
                String imageUrl = imagesResponse.getData().get(0).getUrl();

                service.shutdownExecutor();

                // 生成成功，更新Redis状态和URL
                GenerateResultVO successResultVO = new GenerateResultVO(taskId, null, imageUrl, "Success");
                redisUtil.addToString(key, new Gson().toJson(successResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

            } catch (Exception e) {
                log.error("Doubao image generation failed for taskId: {}", taskId, e);
                LambdaQueryWrapper<Account> select = new LambdaQueryWrapper<Account>().eq(Account::getId, userId).select(Account::getLanguage);
                Account account = accountService.getOne(select);

                // 根据用户语言设置默认错误信息
                String errorMessage = "图像生成失败，请稍后重试"; // 默认中文
                String userLanguage = "CHINESE_SIMPLIFIED"; // 默认语言

                if (account != null && account.getLanguage() != null) {
                    userLanguage = account.getLanguage();
                    if (account.getLanguage().equals("ENGLISH")) {
                        errorMessage = "Image generation failed, please try again later";
                    } else if (account.getLanguage().equals("CHINESE_SIMPLIFIED")) {
                        errorMessage = "图像生成失败，请稍后重试";
                    }
                }
                String errorCode = null;

                // 检查是否为ArkHttpException，提取code和detailMessage
                try {
                    // 通过反射获取code字段
                    java.lang.reflect.Field codeField = e.getClass().getDeclaredField("code");
                    codeField.setAccessible(true);
                    Object codeValue = codeField.get(e);
                    if (codeValue != null) {
                        errorCode = codeValue.toString();
                    }

                    // 通过反射获取detailMessage字段
                    e.printStackTrace();
                    String detailMessageValue = e.getMessage();
                    if (detailMessageValue != null) {
                        String detailMessage = detailMessageValue.toString();

                        // 根据错误码返回相应的用户友好错误信息
                        errorMessage = getDoubaoErrorMessage(errorCode, detailMessage, userLanguage);
                    }
                } catch (Exception reflectionException) {
                    reflectionException.printStackTrace();
                    log.warn("Failed to extract error details from ArkHttpException: {}", reflectionException.getMessage());
                    // 如果反射失败，尝试从异常消息中提取信息
                    String exceptionMessage = e.getMessage();
                    if (exceptionMessage != null) {
                        boolean isEnglish = "ENGLISH".equals(userLanguage);
                        if (exceptionMessage.contains("OutputImageSensitiveContentDetected")) {
                            errorMessage = isEnglish ? "Generated image may contain sensitive content, please try with different input" : "生成的图像可能包含敏感信息，请更换输入内容后重试";
                        } else if (exceptionMessage.contains("InputTextSensitiveContentDetected")) {
                            errorMessage = isEnglish ? "Input text may contain sensitive content, please try again" : "输入文本可能包含敏感信息，请更换后重试";
                        } else if (exceptionMessage.contains("InputImageSensitiveContentDetected")) {
                            errorMessage = isEnglish ? "Input image may contain sensitive content, please try again" : "输入图像可能包含敏感信息，请更换后重试";
                        } else if (exceptionMessage.contains("SensitiveContentDetected")) {
                            errorMessage = isEnglish ? "Input content may contain sensitive information, please try again" : "输入内容可能包含敏感信息，请更换后重试";
                        } else if (exceptionMessage.contains("MissingParameter")) {
                            errorMessage = isEnglish ? "Request parameters are incomplete, please check input" : "请求参数不完整，请检查输入内容";
                        } else if (exceptionMessage.contains("InvalidParameter")) {
                            errorMessage = isEnglish ? "Request parameters are invalid, please check input format" : "请求参数无效，请检查输入格式";
                        } else if (exceptionMessage.contains("InvalidImageURL")) {
                            errorMessage = isEnglish ? "Image format is incorrect or data is corrupted, please re-upload" : "图片格式不正确或数据损坏，请重新上传";
                        } else if (exceptionMessage.contains("OutofContextError")) {
                            errorMessage = isEnglish ? "Input content is too long, please shorten text or image size" : "输入内容过长，请缩短文本或图片尺寸";
                        } else if (exceptionMessage.contains("AuthenticationError")) {
                            errorMessage = isEnglish ? "Service authentication failed, please contact administrator" : "服务认证失败，请联系管理员";
                        } else if (exceptionMessage.contains("TooManyRequests") || exceptionMessage.contains("429")) {
                            errorMessage = isEnglish ? "Too many requests, please try again later" : "请求过于频繁，请稍后重试";
                        } else if (exceptionMessage.contains("InternalServerError") || exceptionMessage.contains("500")) {
                            errorMessage = isEnglish ? "Image generation service is temporarily unavailable, please try again later" : "图像生成服务暂时不可用，请稍后重试";
                        }
                    }
                }

                // 更新Redis状态为失败
                GenerateResultVO failResultVO = new GenerateResultVO(taskId, null, errorMessage, "Fail");
                redisUtil.addToString(key, new Gson().toJson(failResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

                // 记录详细错误信息用于调试
                log.error("Doubao API error - taskId: {}, errorCode: {}, errorMessage: {}",
                        taskId, errorCode, errorMessage);

            }
        });

        return taskId;
    }


    private String getPrintboardPrompt(String style, String prompt) {
        if ("Painting Style".equals(style)) {
            prompt = "1.Requirements: Create a seamless, tiling fashion printboard pattern for apparel. The output must be stylish, contemporary, and suitable for real garment printing. Design pattern, seamless, highly detailed, elegant composition, visually balanced, professional textile print\n" +
                    "2.Core Theme: " + prompt + "\n" +
                    "3.Style: painting_style-The painting style refers to the overall approach, techniques, and artistic philosophy used in the artwork. For fashion designs that will be applied to printboards, it is important to define the unique stylistic elements that would translate well into wearable patterns.";
        } else if ("Illustration Style".equals(style)) {
            prompt = "1.Requirements: Create a seamless, tiling fashion printboard pattern for apparel. The output must be stylish, contemporary, and suitable for real garment printing. Design pattern, seamless, highly detailed, elegant composition, visually balanced, professional textile print\n" +
                    "2.Core Theme: " + prompt + "\n" +
                    "3.Style: illustration_style-Illustration style focuses on the visual storytellingaspect, often used to depict narratives, characters, or thematic concepts. Forfashion, this style can introduce vivid and artistic interpretations, often aligned with specific themes.";
        } else if ("Real Style".equals(style)) {
            prompt = "1.Requirements: Create a seamless, tiling fashion printboard pattern for apparel. The output must be stylish, contemporary, and suitable for real garment printing. Design pattern, seamless, highly detailed, elegant composition, visually balanced, professional textile print\n" +
                    "2.Core Theme: " + prompt + "\n" +
                    "3.Style: real_style-Real style in fashion is all about authenticity. It featuresnatural fabrics, simple cuts that mirror real life silhouettes, and colors inspired bythe everyday world, exuding a down-to-earth and genuine charm.";
        }
        return prompt;
    }

    /**
     * 判断是否为flux模型且类型为Pattern
     */
    private boolean isFluxPatternModel(GenerateThroughImageTextDTO generateDTO) {
        return !StringUtil.isNullOrEmpty(generateDTO.getModelName())
                && "flux".equals(generateDTO.getModelName())
                && "Pattern".equals(generateDTO.getLevel2Type());
    }

    /**
     * 处理flux pattern生成
     */
    private PrepareForGenerateVO handleFluxPatternGeneration(GenerateThroughImageTextDTO generateDTO) {
        // 校验积分是否足够
        validateCredits(CreditsEventsEnum.FLUX_IMG2IMG);

        // 获取图片路径
        String imagePath = geti2iImagePath(generateDTO);

        // 创建生成任务
        String taskId = flux(PATTERN, generateDTO.getText(), imagePath, false);

        // 保存生成记录
        saveGenerateRecord(generateDTO, taskId, imagePath);

        // 处理积分扣除
        processCreditDeduction(generateDTO.getUserId(), taskId, CreditsEventsEnum.FLUX_IMG2IMG);

        return new PrepareForGenerateVO(Collections.singletonList(taskId), 200);
    }

    /**
     * 获取i2i需要传入模型的图片路径
     */
    private String geti2iImagePath(GenerateThroughImageTextDTO generateDTO) {
        if (Objects.isNull(generateDTO.getCollectionElementId())
                || StringUtil.isNullOrEmpty(generateDTO.getDesignType())) {
            return null;
        }

        switch (generateDTO.getDesignType()) {
            case "collection":
                CollectionElement element = collectionElementMapper.selectById(generateDTO.getCollectionElementId());
                return element != null ? element.getUrl() : null;
            case "library":
                Library library = libraryService.getById(generateDTO.getCollectionElementId());
                return library != null ? library.getUrl() : null;
            default:
                return null;
        }
    }

    /**
     * 保存生成记录
     */
    private void saveGenerateRecord(GenerateThroughImageTextDTO generateDTO, String taskId, String imagePath) {
        Generate generate = CopyUtil.copyObject(generateDTO, Generate.class);
        generate.setAccountId(generateDTO.getUserId());
        generate.setUniqueId(taskId);
        generate.setElementSource(generateDTO.getDesignType());
        generate.setElementId(generateDTO.getCollectionElementId());

        // 确定生成类型
        String generateType = determineGenerateType(generateDTO);
        generate.setGenerateType(generateType);
        generate.setModelName("flux");
        generate.setCreateDate(new Date());

        save(generate);
    }

    /**
     * 确定生成类型
     */
    private String determineGenerateType(GenerateThroughImageTextDTO generateDTO) {
        if (Objects.nonNull(generateDTO.getCollectionElementId())) {
            return StringUtil.isNullOrEmpty(generateDTO.getText()) ? "image" : "text-image";
        }
        return "text";
    }

    /**
     * 处理标准生成流程
     */
    private PrepareForGenerateVO handleStandardGeneration(GenerateThroughImageTextDTO generateDTO) {
        // 确定积分事件和生成次数
        GenerationConfig config = determineGenerationConfig(generateDTO);

        // 对Slogan功能限流
        if (config.creditsEvent.equals(CreditsEventsEnum.SLOGAN)) {
            boolean b = redisUtil.allowRequest("Slogan");
            if (!b) {
                return new PrepareForGenerateVO(429);
            }
        }

        // 校验积分是否足够
        validateCredits(config.creditsEvent);

        // 创建生成任务
        List<String> taskIds = createGenerationTasks(generateDTO, config.times);

        // 截取任务id
        String original = taskIds.get(0);
        String taskId = original.substring(0, original.lastIndexOf("-", original.lastIndexOf("-") - 1));

        // 处理积分扣除
        processCreditDeduction(generateDTO.getUserId(), taskId, config.creditsEvent);

        return new PrepareForGenerateVO(taskIds, 200);
    }

    /**
     * 确定生成配置（积分事件和生成次数）
     */
    private GenerationConfig determineGenerationConfig(GenerateThroughImageTextDTO generateDTO) {
        CreditsEventsEnum creditsEvent = CreditsEventsEnum.OTHER;
        int times = 4;

        // 根据不同类型确定配置
        // high -> 生成图片质量高，但生成速度慢，每次生成只返回一张图片
        // fast -> 生成图片质量低，但生成速度快，每次生成返回四张图片
        switch (generateDTO.getLevel1Type()) {
            case "Printboard":
                GenerationConfig generationConfig = handlePrintboardConfig(generateDTO);
                creditsEvent = generationConfig.creditsEvent;
                times = generationConfig.times;
                break;
            case "Moodboard":
                creditsEvent = CreditsEventsEnum.MOOD_BOARD;
                if (isHighModel(generateDTO)) {
                    creditsEvent = CreditsEventsEnum.LOCAL_TEXT2IMG_HIGH;
                    times = 1;
                }
                break;
            case "Sketchboard":
                creditsEvent = CreditsEventsEnum.SKETCH_BOARD;
                if (isHighModel(generateDTO)) {
                    creditsEvent = CreditsEventsEnum.LOCAL_TEXT2IMG_HIGH;
                    times = 1;
                }
                break;
        }

        return new GenerationConfig(creditsEvent, times);
    }

    /**
     * 处理Printboard的特殊配置
     */
    private GenerationConfig handlePrintboardConfig(GenerateThroughImageTextDTO generateDTO) {
        String level2Type = generateDTO.getLevel2Type();
        CreditsEventsEnum creditsEvent = CreditsEventsEnum.OTHER;
        int times = 4;

        if (!CollectionLevel2TypeEnum.printType().contains(level2Type)) {
            throw new BusinessException("unknown.parameter.level2Type");
        }

        switch (level2Type) {
            case "Pattern":
                // Pattern参数校验
                Generate generate = new Generate();
                validateGeneraType(generate, generateDTO.getText(), generateDTO.getCollectionElementId());
                generateDTO.setGenerateType(generate.getGenerateType());

                creditsEvent = CreditsEventsEnum.PATTERN;
                if (isHighModel(generateDTO)) {
                    creditsEvent = CreditsEventsEnum.LOCAL_TEXT2IMG_HIGH;
                    times = 1;
                }
                break;

            case "Slogan":
                validateSloganParams(generateDTO);
                processSloganImage(generateDTO);
                creditsEvent = CreditsEventsEnum.SLOGAN;
                times = 1;
                break;

            case "Logo":
                validateLogoParams(generateDTO);
                generateDTO.setSeed(String.valueOf(random.nextInt(501)));
                creditsEvent = CreditsEventsEnum.LOGO;
                times = 1;
                break;
        }

        return new GenerationConfig(creditsEvent, times);
    }

    /**
     * 校验Slogan参数
     */
    private void validateSloganParams(GenerateThroughImageTextDTO generateDTO) {
        if (StringUtil.isNullOrEmpty(generateDTO.getSloganBase64())) {
            log.error("Printboard-Slogan模式下，slogan image为空");
            throw new BusinessException("slogan.image.cannot.be.empty");
        }
        if (StringUtil.isNullOrEmpty(generateDTO.getText())) {
            log.error("Printboard-Slogan模式下，slogan text为空");
            throw new BusinessException("slogan.style.cannot.be.empty");
        }
    }

    /**
     * 处理Slogan图片上传
     */
    private void processSloganImage(GenerateThroughImageTextDTO generateDTO) {
        // 上传图片到服务器
        String path = minioUtil.base64UploadToPath(generateDTO.getSloganBase64(), sloganBucket, null);
        String name = path.substring(path.lastIndexOf("/") + 1, path.lastIndexOf("."));

        // 保存到数据库
        CollectionElement element = new CollectionElement();
        element.setAccountId(generateDTO.getUserId());
        element.setCollectionId(0L);
        element.setLevel1Type(PRINT_BOARD.getRealName());
        element.setLevel2Type(CollectionLevel2TypeEnum.SLOGAN.getRealName());
        element.setName(name);
        element.setUrl(path);
        element.setHasPin((byte) 0);
        element.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(path, 24 * 60), Boolean.FALSE));
        element.setCreateDate(DateUtil.getByTimeZone(generateDTO.getTimeZone()));
        collectionElementService.save(element);

        // 更新DTO
        generateDTO.setCollectionElementId(element.getId());
        generateDTO.setSloganBase64(null);
        generateDTO.setDesignType("collection");
    }

    /**
     * 校验Logo参数
     */
    private void validateLogoParams(GenerateThroughImageTextDTO generateDTO) {
        if (StringUtil.isNullOrEmpty(generateDTO.getText().trim())) {
            throw new BusinessException("please.input.the.prompt");
        }
    }

    /**
     * 判断是否为high模型
     */
    private boolean isHighModel(GenerateThroughImageTextDTO generateDTO) {
        return !StringUtil.isNullOrEmpty(generateDTO.getModelName())
                && "high".equals(generateDTO.getModelName());
    }

    /**
     * 校验积分是否足够
     */
    private void validateCredits(CreditsEventsEnum creditsEvent) {
        if (!creditsService.creditsPreDeduction(creditsEvent, 1)) {
            throw new BusinessException("remaining.credits.insufficient", ResultEnum.WARNING.getCode());
        }
    }

    /**
     * 创建生成任务
     */
    private List<String> createGenerationTasks(GenerateThroughImageTextDTO generateDTO, int times) {
        String uuid = UUID.randomUUID().toString();
        List<String> taskIds = new ArrayList<>();

        // 特殊处理：某些情况下需要清空modelName
        if ("Printboard".equals(generateDTO.getLevel1Type())
                && !"Pattern".equals(generateDTO.getLevel2Type())) {
            // Logo 和 Slogan 没有模型可选
            generateDTO.setModelName(null);
        }

        for (int i = 1; i <= times; i++) {
            String taskId = uuid + "-" + i + "-" + generateDTO.getUserId();
            taskIds.add(taskId);
            generateDTO.setUniqueId(taskId);

            // 序列化为JSON
            String jsonString = JSON.toJSONString(generateDTO);

            // 加入Redis队列
            addToRedisQueue(taskId, jsonString);
        }

        return taskIds;
    }

    /**
     * 添加到Redis队列
     */
    private void addToRedisQueue(String taskId, String jsonString) {
        // 加入排队队列
//        Double maxScore = redisUtil.getMaxScore(consumptionOrderKey);
//        redisUtil.addToZSet(consumptionOrderKey, taskId, maxScore);

        // 加入结果映射
        String key = generateResultKey + ":" + taskId;
        GenerateResultVO resultVO = new GenerateResultVO(taskId, null, null, "Waiting");
        redisUtil.addToString(key, new Gson().toJson(resultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

        // 发布到MQ
        rabbitMQService.publishMessageToGenerate(jsonString);
    }

    /**
     * 处理积分扣除
     */
    private void processCreditDeduction(Long userId, String taskId, CreditsEventsEnum creditsEvent) {
        // 添加到Redis
        creditsService.addRecordToCreditsDeduction(userId, taskId, creditsEvent);
        // 预插入到数据库
        creditsService.preInsert(userId, creditsEvent.getName(), taskId, Boolean.TRUE, null);
    }

// ============== 配置类 ==============

    /**
     * 生成任务配置类
     * 包含积分事件类型和生成次数
     */
    private static class GenerationConfig {
        final CreditsEventsEnum creditsEvent;
        final int times;

        GenerationConfig(CreditsEventsEnum creditsEvent, int times) {
            this.creditsEvent = creditsEvent;
            this.times = times;
        }
    }

    @Override
    public Long getRankPosition(String uniqueId) {
        // rank 从0开始
        return redisUtil.getRank(consumptionOrderKey, uniqueId);
    }

    @Override
    public List<GenerateResultVO> getGenerateResultList(List<String> taskIdList) {
        List<GenerateResultVO> results = new ArrayList<>();
        Set<String> collect = new HashSet<>();
        boolean flag = true;
        String type = null;
        for (String taskId : taskIdList) {
            String key = generateResultKey + ":" + taskId;
            GenerateResultVO generateResultVO = new Gson().fromJson(redisUtil.getFromString(key), GenerateResultVO.class);

            if (flag) {
                type = resolveModelType(taskId, null);
                flag = false;
            }
            // 暂定万象每次生成1个
            if (type.equals("wx") || type.contains("qwen")) {
                return Collections.singletonList(getAsyncTaskResult(taskId));
            } else if (type.contains("imagen") || type.contains("gemini")) {
                return Collections.singletonList(getGoogleAsyncResult(taskId));
            } else if (type.contains("doubao")) {
                return Collections.singletonList(getDoubaoAsyncResult(taskId));
            } else if (type.equals("freepik")) {
                results.add(generateResultVO);
                continue;
            } else if (type.equals("flux")) {
                results.add(getFluxResultAndSave(taskId));
                continue;
            }

            if (generateResultVO != null && !StringUtil.isNullOrEmpty(generateResultVO.getUrl())) {
                String url = generateResultVO.getUrl();
                if (url.substring(url.lastIndexOf("/") + 1).equals("white_image.jpg")) {
                    generateResultVO.setStatus("Invalid");
                } else {
                    generateResultVO.setUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
                }
            } else if (generateResultVO == null) {
                generateResultVO = new GenerateResultVO();
            }

            if (!StringUtil.isNullOrEmpty(generateResultVO.getStatus())) {
                collect.add(generateResultVO.getStatus());
            }
            results.add(generateResultVO);
        }

        if (taskIdList.size() == 4 && collect.size() == 1 && collect.contains("Fail")) {
            log.info("当前4个生成结果均为失败");
            throw new BusinessException("generate.result.below.standard");
        }
        return results;
    }


    public Generate selectByUniqueId(String uniqueId) {
        QueryWrapper<Generate> qw = new QueryWrapper<>();
        qw.eq("unique_id", uniqueId);

        return getOne(qw);
    }

    public List<Generate> selectListByUniqueId(String uniqueId) {
        QueryWrapper<Generate> qw = new QueryWrapper<>();
        qw.eq("unique_id", uniqueId).orderByDesc("id");

        return baseMapper.selectList(qw);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelGenerate(Long userId, List<String> uniqueIdList, String timeZone, String type) {
        // todo 取消待优化
        uniqueIdList.forEach(uniqueId -> {
            // 1、将需要取消的唯一id加入redis，以便及时取消生成
            redisUtil.addToSet(cancelSetKey, uniqueId, CommonConstant.REDIS_SET_EXPIRE_TIME);

            /*// 1、确认当前消息是否还在排队中
            Boolean exists = redisUtil.isElementExistsInZSet(consumptionOrderKey, uniqueId);
            Boolean flag = Boolean.FALSE;
            if (exists) flag = redisUtil.getRank(consumptionOrderKey, uniqueId) > 1L ? Boolean.TRUE : Boolean.FALSE;
            // 不管flag的默认值是true还是false，只要exists为false，&& 将短路
            if (exists && flag) {
                // 1.1、将需要取消的唯一id加入redis，以便及时取消生成
                redisUtil.addToSet(cancelSetKey, uniqueId);
                // 1.2 将需要取消的id从redis的ConsumptionOrder中删除
                redisUtil.removeFromZSet(consumptionOrderKey, uniqueId);
            } else {
                // 2、判断该消息是否异常
                boolean hasKey = redisUtil.isElementExistsInMap(exceptionMapKey, uniqueId);
                // 3、判断该消息是否已经消费结束
                Boolean existsInResult = redisUtil.isElementExistsInMap(resultMapKey, uniqueId);
                if (!hasKey && !existsInResult) {
                    // 设置取等待状态为false
                    AsyncCallerUtil.waitingStatus.put(uniqueId, false);
                    // 3、直接发送取消请求到python端
                    pythonService.cancelGenerateTask(uniqueId);
                }
            }*/
            String path;
            if (type.equals("Logo")) {
                path = CommonConstant.GENERATE_LOGO_SINGLE_CANCEL;
            } else if (type.equals("PoseTransformation")) {
                path = CommonConstant.POSE_TRANSFORMATION_CANCEL;
            } else {
                path = CommonConstant.GENERATE_CANCEL;
            }

            String key = generateResultKey + ":" + uniqueId;
            GenerateResultVO generateResultVO = new Gson().fromJson(redisUtil.getFromString(key), GenerateResultVO.class);
            if (Objects.isNull(generateResultVO)) {
                log.warn("任务不存在，无法取消");
                return;
            }
            // 判断当前task的状态是不是Fail
            if (!generateResultVO.getStatus().equals("Fail")) {
                // 2、不是，直接发送取消请求到python端
                pythonService.cancelGenerateTask(uniqueId, path);
                // 3、更改result中当前taskId的状态
                redisUtil.addToString(key, new Gson().toJson(new GenerateResultVO(uniqueId, null, null, "Cancelled")), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
            }

            // 3、考虑加一张表，专门用于记录哪些用户在什么时间进行了取消操作,包括已经异常的请求
            GenerateCancel generateCancel = new GenerateCancel(userId, uniqueId, DateUtil.getByTimeZone(timeZone));
            generateCancelMapper.insert(generateCancel);
        });


    }

    @Override
    public void processRelightResult(String taskId, String url, String category) {
        QueryWrapper<ToProductImageResult> qw = new QueryWrapper<>();
        qw.lambda().eq(ToProductImageResult::getTaskId, taskId);
        List<ToProductImageResult> toProductImageResults = toProductImageResultMapper.selectList(qw);
        if (CollectionUtils.isEmpty(toProductImageResults)) {
            return;
//            throw new BusinessException("");
        }
        ToProductImageResult toProductImageResult = toProductImageResults.get(0);
        if (toProductImageResult.getBrightenValue() != null && toProductImageResult.getBrightenValue() != 1.0) {
            pythonService.bright(url, toProductImageResult.getBrightenValue());
        }
        toProductImageResult.setUrl(url);
        toProductImageResult.setStatus("Success");
//        toProductImageResult.setResultType("Relight");
        toProductImageResultMapper.updateById(toProductImageResult);

        String key = relightResultKey + ":" + taskId;
        String imageName = url.substring(url.lastIndexOf("/") + 1);
        String status = imageName.equals("white_image.jpg") ? "Invalid" : "Success";
        GenerateResultVO generateResultVO = new GenerateResultVO(taskId, toProductImageResult.getId(), url, status, category);
        redisUtil.addToString(key, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

        Long accountId = Long.parseLong(taskId.substring(taskId.lastIndexOf("-") + 1));
        if (!status.equals("Invalid")) {
            // 4、扣除积分
            Boolean b = creditsService.taskCreditsDeduction(accountId, taskId);
            // 3、记录积分变更
            if (b) creditsService.insertToCreditsDetail(accountId,
                    CreditsEventsEnum.RELIGHT.getName(),
                    CreditsEventsEnum.RELIGHT.getValue(),
                    "negative", null);
        }
    }

    // 判断试用用户试用generate机会是否使用完毕 每个board 3次机会
    private int getTrialsCount(Long userId, String level1Type) {
        List<Generate> getGenerateList = getGenerateByAccountId(userId, level1Type);
        int trialsCount;
        if (getGenerateList.isEmpty()) {
            trialsCount = 0;
        } else if (getGenerateList.size() == 1 || (getGenerateList.size() >= 4 && getGenerateList.size() / 4 == 1)) {
            trialsCount = 1;
        } else if (getGenerateList.size() == 2 || (getGenerateList.size() >= 4 && getGenerateList.size() / 4 == 2)) {
            trialsCount = 2;
        } else {
            trialsCount = 2;
        }
        return trialsCount;
    }

    public List<Generate> getGenerateByAccountId(Long accountId, String level1Type) {
        QueryWrapper<Generate> qw = new QueryWrapper<>();
        qw.eq("account_id", accountId);
        qw.eq("level1_type", level1Type);

        return baseMapper.selectList(qw);
    }

    public List<Map<String, Object>> getCountByUserAndTime(String startTime, String endTime, List<Long> accountIdList) {
        List<Map<String, Object>> byTypeAndTime = baseMapper.getByTypeAndTime(startTime, endTime, accountIdList);
        return byTypeAndTime;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public GenerateResultVO imageToSketch(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, Long projectId) {

        Long accountId = UserContext.getUserHolder().getId();
        log.info("imageToSketch parameter : {}", imageToSketchDTO);

        // 检查积分是否够本次扣除
        CreditsEventsEnum event = CreditsEventsEnum.IMAGE_TO_SKETCH;
        Boolean b = creditsService.checkCredits(accountId, event, 1);
        if (!b) {
            throw new BusinessException("remaining.credits.insufficient", ResultEnum.PROMPT.getCode());
        }

        String style = imageToSketchDTO.getStyle();
        String styleCode = style.equals(SketchStyle.THICK.getValue()) ? "1" :
                style.equals(SketchStyle.MEDIUM.getValue()) ? "2" :
                        style.equals(SketchStyle.THIN.getValue()) ? "3" : "Custom";

        // 线稿提取
        String sketchPath = requestSketchExtract(imageToSketchDTO, collagePictureUrl, accountId, styleCode);
        // 存数据库
        Generate generate = saveExtractSketchRequest(imageToSketchDTO, collagePictureUrl, projectId,
                accountId, styleCode, "local", "0");
        GenerateResultVO generateResultVO = saveExtractSketchResult(generate, sketchPath, imageToSketchDTO.getGender());
        // 积分扣除
        doCreditsSubtract(accountId, event);

        return generateResultVO;
    }

    private String requestSketchExtract(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl,
                                        Long accountId, String styleCode) {
        String imagePath;
        if (StringUtil.isNullOrEmpty(collagePictureUrl)) {
            CollectionElement collectionElement = collectionElementService.getById(imageToSketchDTO.getElementId());
            imagePath = collectionElement.getUrl();
        } else {
            imagePath = collagePictureUrl;
        }

        log.info(minioUtil.getPreSignedUrl(imagePath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
        String imageName = UUID.randomUUID().toString();
        String objectName = accountId + "/imageToSketch/" + imageName;

        String styleImage;
        if (!Objects.isNull(imageToSketchDTO.getStyleImageId())) {
            CollectionElement styleElement = collectionElementService.getById(imageToSketchDTO.getElementId());
            styleImage = styleElement.getUrl();
        } else {
            styleImage = "";
        }
        String sketchPath = pythonService.imageToSketch(imagePath, userBucket, objectName, styleCode, styleImage);
        log.info("初步图片提取结果：{}", sketchPath);
        return sketchPath;
    }

    private Generate saveExtractSketchRequest(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl,
                                              Long projectId, Long accountId, String styleCode,
                                              String modelName, String taskId) {
        // 存DB
        Generate generate = new Generate();
        generate.setAccountId(accountId);
        generate.setUniqueId(taskId);
        generate.setLevel1Type(SKETCH_BOARD.getRealName());
        generate.setLevel2Type("ImageToSketch");
        generate.setElementSource("collection");
        generate.setElementId(imageToSketchDTO.getElementId());
        generate.setGenerateType("image(" + imageToSketchDTO.getGender() + ")");
        generate.setModelName(modelName);
        generate.setSketchStyle(styleCode);
        generate.setStyleImageElementId(imageToSketchDTO.getStyleImageId());
        generate.setProjectId(projectId);
        generate.setInputImageUrl(collagePictureUrl);
        generate.setCreateDate(new Date());
        baseMapper.insert(generate);
        return generate;
    }

    public GenerateResultVO saveExtractSketchResult(Generate generate, String sketchPath, String gender) {
        // 将生成结果存入DB
        GenerateDetail generateDetail = new GenerateDetail();
        generateDetail.setGenerateId(generate.getId());
        generateDetail.setUrl(sketchPath);
        generateDetail.setIsLike((byte) 0);
        generateDetail.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(sketchPath, 24 * 60), Boolean.FALSE));
        generateDetail.setCreateDate(LocalDateTime.now());
        generateDetailMapper.insert(generateDetail);

        String clothCategory = pythonService.getClothCategory(sketchPath, gender);

        return new GenerateResultVO(generate.getUniqueId(), generateDetail.getId(),
                minioUtil.getPreSignedUrl(sketchPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), "Success", clothCategory);
    }

    public void doCreditsSubtract(Long accountId, CreditsEventsEnum event) {
        Account account = accountService.getById(accountId);
        BigDecimal existingCredits = account.getCredits();
        BigDecimal subtract = existingCredits.subtract(new BigDecimal(event.getValue()));
        BigDecimal creditsUsage = null;
        if (!StringUtil.isNullOrEmpty(account.getOrganizationName())) {
            creditsUsage = Objects.isNull(account.getCreditsUsage()) ? BigDecimal.ZERO : account.getCreditsUsage();
            creditsUsage = creditsUsage.add(new BigDecimal(event.getValue()));
        }
        accountService.updateCreditsAndEndTime(account, subtract.toString(), null, creditsUsage);
        creditsService.preInsert(accountId, event.getName(), null, Boolean.FALSE, event.getValue());
    }

    // 注入线程池（可在配置类中定义）
    @Resource
    private Executor asyncTaskExecutor;

    public String imageToSketchAsync(ImageToSketchDTO imageToSketchDTO, String collagePictureUrl, Long projectId) {
        Long accountId = UserContext.getUserHolder().getId();
        log.info("imageToSketch parameter : {}", imageToSketchDTO);
        // 目前没有用其他提取模型，暂时写死flux
        imageToSketchDTO.setModelName("flux");

        // 检查积分是否够本次扣除
//        CreditsEventsEnum event = CreditsEventsEnum.IMAGE_TO_SKETCH;
        CreditsEventsEnum event = CreditsEventsEnum.IMAGE_TO_SKETCH_FLUX;
        Boolean b = creditsService.checkCredits(accountId, event, 1);
        if (!b) {
            throw new BusinessException("remaining.credits.insufficient", ResultEnum.PROMPT.getCode());
        }

        // 生成唯一任务ID
        String taskId;
        if (!StringUtil.isNullOrEmpty(imageToSketchDTO.getModelName())
                && imageToSketchDTO.getModelName().equals("flux")) {
            String imagePath;
            // todo 拼贴图的线稿提取是否能用flux
            if (StringUtil.isNullOrEmpty(collagePictureUrl)) {
                CollectionElement collectionElement = collectionElementService.getById(imageToSketchDTO.getElementId());
                imagePath = collectionElement.getUrl();
            } else {
                imagePath = collagePictureUrl;
            }
            taskId = flux(event, null, imagePath, false);
            // 存数据库
            saveExtractSketchRequest(imageToSketchDTO, collagePictureUrl, projectId,
                    accountId, imageToSketchDTO.getStyle(), "flux", taskId);

            // 6、添加预扣除积分到redis
            creditsService.addRecordToCreditsDeduction(accountId, taskId, event);
            // 6.1 添加积分扣除记录到db
            creditsService.preInsert(accountId, event.getName(), taskId, Boolean.TRUE, null);

            return taskId;
        }

        taskId = UUID.randomUUID().toString();
        // 异步执行耗时操作(由于prompt提取耗时较长，页面暂时只提供flux生成,废弃以下部分)
        CompletableFuture.runAsync(() -> {
            try {
                processImageToSketch(taskId, imageToSketchDTO, collagePictureUrl, projectId, accountId, event);
            } catch (Exception e) {
                log.error("异步处理图片转sketch失败, taskId: {}", taskId, e);
                // 更新redis
                redisUtil.addToString(generateResultKey + ":" + taskId, new Gson().toJson(new GenerateResultVO(taskId, "Fail")), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
            }
        }, asyncTaskExecutor);
        return taskId;
    }

    private void processImageToSketch(String taskId, ImageToSketchDTO imageToSketchDTO,
                                      String collagePictureUrl, Long projectId,
                                      Long accountId, CreditsEventsEnum event) throws IOException {
        // 设置任务状态为处理中
        redisUtil.addToString(generateResultKey + ":" + taskId, new Gson().toJson(new GenerateResultVO(taskId, "Executing")), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
        String style = imageToSketchDTO.getStyle();
        String styleCode = style.equals(SketchStyle.THICK.getValue()) ? "1" :
                style.equals(SketchStyle.MEDIUM.getValue()) ? "2" :
                        style.equals(SketchStyle.THIN.getValue()) ? "3" : "Custom";
        // 请求记录存数据库
        Generate generate = saveExtractSketchRequest(imageToSketchDTO, collagePictureUrl, projectId,
                accountId, styleCode, "freepik", taskId);
        // 1、初步提取结果
        String sketchPath = requestSketchExtract(imageToSketchDTO, collagePictureUrl, accountId, styleCode);
        // 2、获取输入图的描述
        String imageDescription = getImageDescription(sketchPath);
        // 3、请求freepik reimage
        String dataStr = reimagineFreePik(sketchPath, imageDescription, "vivid");
        if (StringUtil.isNullOrEmpty(dataStr)) {
            throw new BusinessException("extract sketch failed");
        }

        JSONObject data = JSONUtil.parseObj(dataStr);
        String upgradeImageUrl = data.getBeanList("generated", String.class).get(0);
        String freepikTaskId = data.getStr("task_id");

        // 4、下载图片
        byte[] bytes = downloadVideoOrImage(upgradeImageUrl);
//        byte[] bytes = downloadWithProxy(upgradeImageUrl);
        // 5、上传图片到minio保存
        String objectName = accountId + "/imageToSketch/" + freepikTaskId + ".png";
        minioUtil.uploadToMinio(bytes, userBucket, objectName, "image/png");
        // 6、保存结果到db
        GenerateResultVO generateResultVO = saveExtractSketchResult(generate, userBucket + "/" + objectName, imageToSketchDTO.getGender());
        // 7、积分扣除
        doCreditsSubtract(accountId, event);
        // 8、将结果存入Redis
        redisUtil.addToString(generateResultKey + ":" + taskId, new Gson().toJson(generateResultVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
    }

    // 对提取出来的sketch做调整
    // 输入 base64，以及 性别 分类，将图片添加到library
    @Override
    @Transactional(rollbackFor = Exception.class)
    public GenerateResultVO modifySketch(GenerateModifyDTO generateModifyDTO) {
        log.info("修改生成或library中的sketch或print，并加入到library");

        // 提取常用参数
        Long accountId = UserContext.getUserHolder().getId();
        String base64 = generateModifyDTO.getBase64();
        String gender = generateModifyDTO.getGender().toLowerCase();
        String category = generateModifyDTO.getCategory();
        Long originalId = generateModifyDTO.getOriginalId();
        String originalIdSource = generateModifyDTO.getOriginalIdSource();
        boolean isOverride = generateModifyDTO.getIsOverride();
        boolean isSketch = generateModifyDTO.getType().equals(SKETCH_BOARD.getRealName());

        // 获取原始路径和可能的generateId
        PathInfo pathInfo = getOriginalPathAndGenerateId(originalIdSource, originalId);
        if (Objects.isNull(pathInfo)) {
            throw new BusinessException("unknown sourceIdType", ResultEnum.PROMPT.getCode());
        }

        // 确定存储路径
        String storagePath = isOverride
                ? pathInfo.originalPath.replaceFirst("^[^/]+/", "").replaceFirst("\\.[^.]+$", "")
                : isSketch
                ? accountId + "/sketchboard/" + gender + "/" + category + "/" + UUID.randomUUID()
                : accountId + "/printboard/" + UUID.randomUUID();

        // 上传到MinIO
        String minioPath = minioUtil.base64UploadToPath(base64, userBucket, storagePath);
        log.info("修改后的图片：{}", minioPath);

        // 保存到数据库并返回结果
        return originalIdSource.equals("Library")
                ? handleLibrarySave(accountId, originalId, minioPath, category, gender, isOverride, generateModifyDTO.getType())
                : originalIdSource.equals("Generate")
                ? handleGenerateSave(originalId, pathInfo.generateId, minioPath, category, isOverride)
                : handleUploadSave(accountId, originalId, minioPath, category, generateModifyDTO.getGender(), isOverride, generateModifyDTO.getType());
    }

    private static class PathInfo {
        String originalPath;
        Long generateId;

        PathInfo(String originalPath, Long generateId) {
            this.originalPath = originalPath;
            this.generateId = generateId;
        }
    }

    private PathInfo getOriginalPathAndGenerateId(String originalIdSource, Long originalId) {
        switch (originalIdSource) {
            case "Library":
                return new PathInfo(libraryService.getById(originalId).getUrl(), null);
            case "Generate":
                GenerateDetail detail = generateDetailMapper.selectById(originalId);
                return new PathInfo(detail.getUrl(), detail.getGenerateId());
            case "Collection":
                CollectionElement collectionElement = collectionElementMapper.selectById(originalId);
                return new PathInfo(collectionElement.getUrl(), null);
            default:
                return null;
        }
    }

    private GenerateResultVO handleLibrarySave(Long accountId, Long libraryId, String minioPath,
                                               String category, String gender, boolean isOverride, String level1Type) {
        Library library;
        String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false);
        if (isOverride) {
            library = new Library();
            library.setId(libraryId);
            library.setUrl(minioPath);
            library.setUpdateDate(new Date());
            libraryService.updateById(library);
        } else {
            library = new Library(accountId, level1Type, category, gender, minioPath, md5, new Date());
            libraryService.save(library);
            libraryId = library.getId();
        }
        return buildResultVO(libraryId, minioPath, category);
    }

    private GenerateResultVO handleGenerateSave(Long originalId, Long generateId, String minioPath,
                                                String category, boolean isOverride) {
        GenerateDetail generateDetail = new GenerateDetail();
        String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true), Boolean.FALSE);
        if (isOverride) {
            generateDetail.setId(originalId);
            generateDetail.setUrl(minioPath);
            generateDetail.setUpdateDate(new Date());
            generateDetailMapper.updateById(generateDetail);
        } else {
            generateDetail.setGenerateId(generateId);
            generateDetail.setUrl(minioPath);
            generateDetail.setIsLike((byte) 0);
            generateDetail.setMd5(md5);
            generateDetail.setCreateDate(LocalDateTime.now());
            generateDetailMapper.insert(generateDetail);
            originalId = generateDetail.getId();
        }
        return buildResultVO(originalId, minioPath, category);
    }

    private GenerateResultVO handleUploadSave(Long accountId, Long originalId, String minioPath,
                                              String category, String gender, boolean isOverride, String level1Type) {
        CollectionElement collectionElement;
        String md5 = MD5Utils.encryptFile(minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false);
        if (isOverride) {
            collectionElement = new CollectionElement();
            collectionElement.setId(originalId);
            collectionElement.setUrl(minioPath);
            collectionElement.setMd5(md5);
            collectionElement.setUpdateDate(new Date());
            collectionElementMapper.updateById(collectionElement);
        } else {
            CollectionElement originalElement = collectionElementMapper.selectById(originalId);
            String name = minioPath.substring(minioPath.lastIndexOf("/") + 1, minioPath.lastIndexOf("."));
            if ("Sketchboard".equals(level1Type)) {
                collectionElement = new CollectionElement(accountId, level1Type, category, gender, name, minioPath, (byte) 0, md5, new Date(), originalElement.getProjectId());
            } else {
                collectionElement = new CollectionElement(accountId, level1Type, category, name, minioPath, (byte) 0, md5, new Date(), originalElement.getProjectId());
            }
            collectionElementMapper.insert(collectionElement);
        }
        return buildResultVO(collectionElement.getId(), minioPath, category);
    }

    private GenerateResultVO buildResultVO(Long id, String minioPath, String category) {
        String url = minioUtil.getPreSignedUrl(minioPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME, true);
        return new GenerateResultVO(id, url, "Success", category);
    }

    public ToProductImageResultVO poseTransform(PoseTransformDTO poseTransformDTO) {
        // 参数判断
        handleMotionParams(poseTransformDTO);

        Long accountId = UserContext.getUserHolder().getId();
        Long projectId = poseTransformDTO.getProjectId();
        String productImage = poseTransformDTO.getProductImage();
        Integer poseId = poseTransformDTO.getPoseId();
        boolean wxTask = StringUtil.isNullOrEmpty(poseTransformDTO.getModelName()) && poseTransformDTO.getModelName().equals("wx");

        // 1、判断用户当前积分是否够本次生成消耗
        CreditsEventsEnum creditsEventsEnum = wxTask ? CreditsEventsEnum.WX_ANIMATION : CreditsEventsEnum.LOCAL_ANIMATION;
        Boolean preDeduction = creditsService.creditsPreDeduction(creditsEventsEnum, 1);
        if (!preDeduction) {
            throw new BusinessException("remaining.credits.insufficient", ResultEnum.WARNING.getCode());
        }

        // 3、生成唯一id 使用uuid,由于uuid重复的几率很小，故取消对uuid重复性的校验
        String taskId;
        Boolean isRequestSuccess = false;
        PoseTransformation poseTransformation = new PoseTransformation();
        if (!StringUtil.isNullOrEmpty(poseTransformDTO.getModelName()) && poseTransformDTO.getModelName().equals("wx")) {
            // 请求生成视频
            taskId = animateAnyone(poseTransformDTO, accountId);
            if (!StringUtil.isNullOrEmpty(taskId)) {
                isRequestSuccess = true;
                apiGenerateService.addAPIGenerateRecordAsync(accountId, taskId, Module.poseTransfer.getValue(), "wx", "Pending");
            }
            poseTransformation.setModelName("wx");
        } else {
            String uuid = UUID.randomUUID().toString();
            taskId = uuid + "-" + accountId;

            com.alibaba.fastjson.JSONObject params = createParamsForMotion(poseTransformDTO, taskId);
            String api = params.getString("api");
            params.remove("api");
            // 请求生成视频
            isRequestSuccess = pythonService.poseTransformation(params, api);
        }

        poseTransformation.setProjectId(projectId);
        poseTransformation.setAccountId(accountId);
        poseTransformation.setUniqueId(taskId);
        poseTransformation.setProductImage(productImage);
        poseTransformation.setLastFrameProductImage(poseTransformDTO.getLastFrameProductImage());
        poseTransformation.setPoseId(poseId);
        poseTransformation.setIsLiked((byte) 0);
        String taskStatus = isRequestSuccess ? "Executing" : "Fail";
        poseTransformation.setTaskStatus(taskStatus);
        poseTransformation.setPrompt(poseTransformDTO.getPrompt());
        poseTransformation.setCreateTime(LocalDateTime.now());
        poseTransformationMapper.insert(poseTransformation);
        // 当需要默认like
        ToProductImageResultVO toProductImageResultVO = new ToProductImageResultVO();
        toProductImageResultVO.setId(poseTransformation.getId());
        toProductImageResultVO.setParentId(poseTransformDTO.getParentId());
        toProductImageResultVO.setResultType(Module.poseTransfer.getValue());
        toProductImageResultVO.setTaskId(taskId);
        toProductImageResultVO.setStatus(taskStatus);
        toProductImageResultVO.setSourceUrl(minioUtil.getPreSignedUrl(productImage, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
        toProductImageResultVO.setPoseId(poseId);
        toProductImageResultVO.setModelName(poseTransformDTO.getModelName());

        if (Objects.nonNull(poseTransformDTO.getIsDefaultLike()) && poseTransformDTO.getIsDefaultLike()) {
            // 满足条件下添加到like
            poseTransformation.setIsLiked((byte) 1);
            poseTransformation.setUpdateTime(LocalDateTime.now());
            poseTransformationMapper.updateById(poseTransformation);
            CollectionSort collectionSort = addPoseTransferLike(poseTransformDTO, poseTransformation.getId());
            Integer reSort = collectionSortService.rearrangeChildSort(poseTransformation.getId(), CollectionType.POSE_TRANSFORM.getValue(),
                    poseTransformDTO.getParentId(), poseTransformDTO.getUserLikeSortId());
            toProductImageResultVO.setSort(Objects.isNull(reSort) ? Objects.isNull(collectionSort) ? null : collectionSort.getSort() : reSort);
            toProductImageResultVO.setParentId(poseTransformDTO.getParentId());
            toProductImageResultVO.setUserLikeSortId(Objects.isNull(collectionSort) ? null : collectionSort.getId());
        } else if (Objects.nonNull(poseTransformDTO.getIsDefaultLike()) && Objects.nonNull(poseTransformDTO.getParentId())) {
            toProductImageResultVO.setParentId(poseTransformDTO.getParentId());
        }


        if (isRequestSuccess) {
            // 6、添加预扣除积分到redis
            creditsService.addRecordToCreditsDeduction(accountId, taskId, creditsEventsEnum);
            // 6.1 添加积分扣除记录到db
            creditsService.preInsert(accountId, creditsEventsEnum.getName(), taskId, Boolean.TRUE, null);
            // 更新项目更新时间
            projectService.modifyProjectUpdateTime(projectId);
            return toProductImageResultVO;
        }
        throw new BusinessException("pose.transformation.error", ResultEnum.ERROR.getCode());
    }

    private void handleMotionParams(PoseTransformDTO poseTransformDTO) {
        if (Objects.isNull(poseTransformDTO.getMode()) || poseTransformDTO.getMode() == 0) {
            throw new BusinessException("Mode cannot be empty");
        }
        MotionModeEnum motionModeEnum = MotionModeEnum.of(poseTransformDTO.getMode());
        if (Objects.isNull(motionModeEnum)) {
            throw new BusinessException("unknown.mode");
        }
        if (poseTransformDTO.getMode() == 1 && Objects.isNull(poseTransformDTO.getPoseId())) {
            throw new BusinessException("Please choose a pose");
        }
        if (poseTransformDTO.getMode() == 3 && StringUtil.isNullOrEmpty(poseTransformDTO.getLastFrameProductImage())) {
            throw new BusinessException("Last frame cannot be empty");
        }
    }


    private CollectionSort addPoseTransferLike(PoseTransformDTO poseTransformDTO, Long poseTransformationId) {
        if (Objects.nonNull(poseTransformDTO.getParentId())
                && !poseTransformDTO.getParentId().equals(0L)) {
            return disOrLikePose(poseTransformationId, "like",
                    poseTransformDTO.getProjectId(), poseTransformDTO.getParentId());
        }
        return null;
    }

    public void processPoseTransformResult(String taskId, String gifUrl, String videoUrl, String imageUrl) {
        // 1、存储模型返回的数据
        PoseTransformation poseTransformation = getPoseTransformationByTaskId(taskId);
        if (Objects.isNull(poseTransformation)) {
            return;
        }

        poseTransformation.setGifUrl(gifUrl);
        poseTransformation.setVideoUrl(videoUrl);
        poseTransformation.setFirstFrameUrl(imageUrl);
        poseTransformation.setTaskStatus("Success");
        poseTransformation.setUpdateTime(LocalDateTime.now());
        poseTransformationMapper.updateById(poseTransformation);

        String key = generateResultKey + ":" + taskId;
        PoseTransformationVO poseTransformationVO = new PoseTransformationVO(
                poseTransformation.getId(), taskId, gifUrl, videoUrl, imageUrl, (byte) 0, "Success");
        poseTransformationVO.setPoseId(poseTransformation.getPoseId());
        poseTransformationVO.setModelName(poseTransformation.getModelName());

        // 2、更新redis
        redisUtil.addToString(key, new Gson().toJson(poseTransformationVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

        // 3、执行积分扣除
        String accountId = taskId.substring(taskId.lastIndexOf("-") + 1);
//        String uuid = taskId.substring(0, taskId.lastIndexOf("-"));
        Boolean flag = creditsService.taskCreditsDeduction(Long.parseLong(accountId), taskId);
        if (flag) creditsService.updateChangedCredits(accountId, taskId);

        // 发消息
        sendSysMsgForPT(poseTransformation);
    }

    // 处理PoseTransformation失败的情况
    public void processPTFailSituation(String taskId) {
        PoseTransformation poseTransformation = getPoseTransformationByTaskId(taskId);
        if (Objects.isNull(poseTransformation)) {
            return;
        }

        // 更新生成记录的状态
        poseTransformation.setTaskStatus("Fail");
        poseTransformation.setUpdateTime(LocalDateTime.now());
        poseTransformationMapper.updateById(poseTransformation);
        // 发消息
        sendSysMsgForPT(poseTransformation);
    }

    private PoseTransformation getPoseTransformationByTaskId(String taskId) {
        QueryWrapper<PoseTransformation> qw = new QueryWrapper<>();
        qw.eq("unique_id", taskId);
        List<PoseTransformation> poseTransformations = poseTransformationMapper.selectList(qw);
        if (poseTransformations != null && poseTransformations.size() > 1) {
            log.warn("通过taskId {} 查询到的PoseTransformation的结果不止一条", taskId);
        } else if (poseTransformations == null || poseTransformations.isEmpty()) {
            return null;
        }
        return poseTransformations.get(0);
    }

    @Override
    public void sendSysMsgForPT(PoseTransformation poseTransformation) {
        // 确认当前任务是否已通知过，是 -> 不再通知；否 -> 通知
        Boolean elementExistsInSet = redisUtil.isElementExistsInSet(RedisUtil.VIDEO_FINISHED_TASKS, poseTransformation.getUniqueId());
        if (elementExistsInSet) {
            // 已通知过，不再通知
            return;
        }

        boolean isSuccess;
        if (!StringUtil.isNullOrEmpty(poseTransformation.getTaskStatus()) && poseTransformation.getTaskStatus().equals("Success")) {
            isSuccess = true;
        } else if (!StringUtil.isNullOrEmpty(poseTransformation.getTaskStatus()) && poseTransformation.getTaskStatus().equals("Fail")) {
            isSuccess = false;
        } else {
            // 不通知
            return;
        }

        Project project = projectService.getById(poseTransformation.getProjectId());
        // 发通知
        if (Objects.nonNull(project) && !StringUtil.isNullOrEmpty(project.getName())) {
            messageCenterService.videoFinishedMsg(poseTransformation.getAccountId(), project.getName(), isSuccess);
            // 添加已通知记录到redis
            redisUtil.addToSet(RedisUtil.VIDEO_FINISHED_TASKS, poseTransformation.getUniqueId(), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
        }
    }

    public List<PoseTransformationVO> getPoseTransformationResult(List<String> taskIdList, Long projectId, Boolean like) {
        List<PoseTransformationVO> resultList = new ArrayList<>();

        // 处理按taskId列表查询的情况
        if (taskIdList != null && !taskIdList.isEmpty()) {
            for (String taskId : taskIdList) {
                PoseTransformationVO vo = buildPoseTransformationVO(taskId, null);
                if (vo != null) {
                    resultList.add(vo);
                }
            }
        }
        // 处理按projectId和like查询的情况
        else if (projectId != null) {
            QueryWrapper<PoseTransformation> queryWrapper = new QueryWrapper<PoseTransformation>()
                    .eq("project_id", projectId)
                    .ne("task_status", "Fail");

            if (like != null) {
                queryWrapper.eq("is_liked", like ? 1 : 0);
            }

            List<PoseTransformation> poseTransformations = poseTransformationMapper.selectList(queryWrapper);

            if (!CollectionUtils.isEmpty(poseTransformations)) {
                for (PoseTransformation item : poseTransformations) {
                    PoseTransformationVO vo = buildPoseTransformationVO(item.getUniqueId(), item);
                    if (vo != null && !isInvalidStatus(vo.getStatus())) {
                        resultList.add(vo);
                    }
                }
            }
        }

        return resultList;
    }

    private PoseTransformationVO buildPoseTransformationVO(String taskId, PoseTransformation dbItem) {
        String type = resolveModelType(taskId, POSE_TRANSFORMATION.getName());
        String key = generateResultKey + ":" + taskId;
        String resultJson = redisUtil.getFromString(key);

        PoseTransformationVO vo;

        // 1. 优先从Redis获取数据
        if (!StringUtil.isNullOrEmpty(resultJson)) {
            vo = new Gson().fromJson(resultJson, PoseTransformationVO.class);

            // 设置数据库中的额外字段
            if (dbItem != null) {
                vo.setId(dbItem.getId());
                vo.setIsLiked(dbItem.getIsLiked());
            }

            // 处理成功状态的数据
            if ("Success".equals(vo.getStatus()) && !"wx".equals(type)) {
                processAllUrls(vo, dbItem);
            }

            vo.setResultType(CollectionType.POSE_TRANSFORM.getValue());
        }
        // 2. 处理wx类型的情况
        else if ("wx".equals(type)) {
            vo = getAnimateResult(taskId);
        }
        // 3. 处理既没有Redis数据也不是wx类型的情况
        else {
            // 如果有数据库记录
            if (dbItem != null) {
                vo = CopyUtil.copyObject(dbItem, PoseTransformationVO.class);
                vo.setTaskId(taskId);

                // 设置产品图片URL(首帧)
                if (dbItem.getProductImage() != null) {
                    vo.setProductImage(minioUtil.getPreSignedUrl(
                            dbItem.getProductImage(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
                }
                // 设置产品图片URL(尾帧)
                if (dbItem.getLastFrameProductImage() != null) {
                    vo.setLastFrameProductImage(minioUtil.getPreSignedUrl(
                            dbItem.getLastFrameProductImage(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
                }

                // 如果视频URL为空，直接返回
                if (StringUtil.isNullOrEmpty(dbItem.getVideoUrl())) {
                    return vo;
                }

                processAllUrls(vo, dbItem);
            }
            // 如果没有数据库记录
            else {
                vo = new PoseTransformationVO(taskId, "Executing");
            }
        }

        AuthPrincipalVo userHolder = UserContext.getUserHolder();
        Long userId = userHolder.getId();
        if (userId != null) {
            // 处理父ID逻辑
            processParentId(vo, dbItem != null ? dbItem :
                    poseTransformationMapper.selectOne(new QueryWrapper<PoseTransformation>().eq("unique_id", taskId).eq("account_id", userId)));

        } else {
            // 处理父ID逻辑
            processParentId(vo, dbItem != null ? dbItem :
                    poseTransformationMapper.selectList(new QueryWrapper<PoseTransformation>().eq("unique_id", taskId)).get(0));

        }

        return vo;
    }

    private void processAllUrls(PoseTransformationVO vo, PoseTransformation dbItem) {
        // 处理各种URL
        processUrl(vo.getGifUrl(), url ->
                vo.setGifUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)));
        processUrl(vo.getVideoUrl(), url ->
                vo.setVideoUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)));
        processUrl(vo.getFirstFrameUrl(), url ->
                vo.setFirstFrameUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)));
    }

    private void processParentId(PoseTransformationVO vo, PoseTransformation poseTransformation) {
        if (poseTransformation != null) {
            ToProductImageResult productResult = getProductResultByPath(poseTransformation);
            if (productResult != null) {
                Long parentId = collectionSortService.getParentIdByElementIdAndElementType(
                        productResult.getId(), CollectionType.TO_PRODUCT_IMAGE.getValue());
                if (Objects.isNull(parentId)) {
                    parentId = toProductImageResultService.getUnlikedResultParentId(null, poseTransformation.getProductImage());
                }
                vo.setParentId(parentId);
                vo.setId(poseTransformation.getId());
                vo.setModelName(poseTransformation.getModelName());
                vo.setRelationType(Module.poseTransfer.getValue());
                vo.setProductImage(minioUtil.getPreSignedUrl(productResult.getUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
            }
        }
    }

    private boolean isInvalidStatus(String status) {
        return "Invalid".equals(status) || "Fail".equals(status);
    }

    public void updatePoseTransferStatus(String taskId, String status, PoseTransformation poseTransformation) {
        if (Objects.isNull(poseTransformation)) {
            poseTransformation = poseTransformationMapper.selectOne(new QueryWrapper<PoseTransformation>().eq("unique_id", taskId));
        }

        if (StringUtil.isNullOrEmpty(poseTransformation.getTaskStatus()) || !status.equals(poseTransformation.getTaskStatus())) {
            poseTransformation.setTaskStatus(status);
            poseTransformation.setUpdateTime(LocalDateTime.now());
            poseTransformationMapper.updateById(poseTransformation);
        }
    }

    private ToProductImageResult getProductResultByPath(PoseTransformation poseTransformation) {
        QueryWrapper<ToProductImageResult> qw = new QueryWrapper<>();
        //增加projectID匹配，避免因为二创导致查询到多条记录
        qw.lambda().eq(ToProductImageResult::getProjectId, poseTransformation.getProjectId());
        qw.lambda().eq(ToProductImageResult::getUrl, poseTransformation.getUniqueId());
        return toProductImageResultMapper.selectOne(qw);
    }

    /*public List<PoseTransformationVO> getPoseTransformationResultList(Long projectId, boolean like) {
        List<PoseTransformation> poseTransformations = poseTransformationMapper.selectList(new QueryWrapper<PoseTransformation>().eq("project_id", projectId)
                .eq("is_liked", like ? 1 : 0).ne("task_status", "Fail"));
        List<PoseTransformationVO> vos = new ArrayList<>();
//        if (poseTransformations != null && poseTransformations.size() > 1){
        if (!CollectionUtils.isEmpty(poseTransformations)) {
            for (PoseTransformation item : poseTransformations) {
                String taskId = item.getUniqueId();
                String key = generateResultKey + ":" + taskId;
                String resultJson = redisUtil.getFromString(key);

                PoseTransformationVO poseTransformationVO;

                if (!StringUtil.isNullOrEmpty(resultJson)) {
                    // 从Redis获取并转换数据
                    poseTransformationVO = new Gson().fromJson(resultJson, PoseTransformationVO.class);
                    poseTransformationVO.setId(item.getId());
                    poseTransformationVO.setIsLiked(item.getIsLiked());

                    // 处理成功状态的数据
                    if ("Success".equals(poseTransformationVO.getStatus())) {
                        poseTransformationVO.setProductImage(
                                minioUtil.getPreSignedUrl(item.getProductImage(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));

                        // 处理各种URL
                        processUrl(poseTransformationVO.getGifUrl(), url ->
                                poseTransformationVO.setGifUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)));
                        processUrl(poseTransformationVO.getVideoUrl(), url ->
                                poseTransformationVO.setVideoUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)));
                        processUrl(poseTransformationVO.getFirstFrameUrl(), url ->
                                poseTransformationVO.setFirstFrameUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)));
                    }

                    // 添加有效数据到结果列表
                    if (!"Invalid".equals(poseTransformationVO.getStatus()) && !"Fail".equals(poseTransformationVO.getStatus())) {
                        vos.add(poseTransformationVO);
                    }
                } else {
                    // 处理Redis中没有缓存的情况
                    poseTransformationVO = CopyUtil.copyObject(item, PoseTransformationVO.class);

                    poseTransformationVO.setTaskId(taskId);
                    poseTransformationVO.setProductImage(
                            minioUtil.getPreSignedUrl(item.getProductImage(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));

                    // todo 面对没有生成结束的情况，返回taskId
                    if (StringUtil.isNullOrEmpty(item.getVideoUrl())) {
                        vos.add(poseTransformationVO);
                        continue;
                    }
                    // 处理各种URL
                    processUrl(poseTransformationVO.getGifUrl(), url ->
                            poseTransformationVO.setGifUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)));
                    processUrl(poseTransformationVO.getVideoUrl(), url ->
                            poseTransformationVO.setVideoUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)));
                    processUrl(poseTransformationVO.getFirstFrameUrl(), url ->
                            poseTransformationVO.setFirstFrameUrl(minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME)));

                    vos.add(poseTransformationVO);
                }
            }
        }
        return vos;
    }*/

    // 辅助方法：处理URL
    private void processUrl(String url, Consumer<String> processor) {
        if (!StringUtil.isNullOrEmpty(url) && !"None".equals(url)) {
            processor.accept(url);
        }
    }

    public CollectionSort disOrLikePose(Long transformedId, String likeOrDislike, Long projectId, Long collectionSortParentId) {
        PoseTransformation poseTransformation = poseTransformationMapper.selectById(transformedId);
        CollectionSort collectionSort = null;
        if (Objects.nonNull(poseTransformation)) {
            if (likeOrDislike.equals("like")) {
                poseTransformation.setIsLiked((byte) 1);
                if (null != collectionSortParentId) {
                    collectionSort = collectionSortService.addCollectionSort(poseTransformation.getId(), CollectionType.POSE_TRANSFORM.getValue(), projectId, collectionSortParentId);
                }
            } else if (likeOrDislike.equals("dislike")) {
                poseTransformation.setIsLiked((byte) 0);
                if (null != collectionSortParentId) {
                    collectionSortService.deleteCollectionSort(poseTransformation.getId(), CollectionType.POSE_TRANSFORM.getValue(), projectId, collectionSortParentId);
                }
            }
            poseTransformation.setUpdateTime(LocalDateTime.now());
            poseTransformationMapper.updateById(poseTransformation);
        }
        if (Objects.nonNull(collectionSort)) {
            projectService.modifyProjectUpdateTime(projectId);
        }
        return collectionSort;
    }

    public String modifyModelProportion(ModifyModelProportionDTO proportionDTO) {
        log.info("modifyModelProportion params: {}", proportionDTO);
        String name;
        String gender;
        Library model = null;
        Long accountId = UserContext.getUserHolder().getId();
        String uuid = UUID.randomUUID().toString();
        // 所有修改的图片都另存为,不覆盖原图
        if (proportionDTO.getType().equals("Library")) {
            model = libraryService.getById(proportionDTO.getId());
            if (Objects.isNull(model)) {
                throw new BusinessException("model.not.found");
            }
            String url = model.getUrl();
            name = url.substring(url.indexOf("/") + 1, url.lastIndexOf("/")) + "/" + uuid;
//            gender = model.getLevel2Type();
        } else {
            SysFileVO sysModel = sysFileService.getById(proportionDTO.getId());
            if (Objects.isNull(sysModel)) {
                throw new BusinessException("model.not.found");
            }
            gender = sysModel.getLevel2Type();
            name = accountId + "/models/" + gender.toLowerCase() + "/" + uuid;
        }
        // 只需要将结果存入library
        String modifiedModel = pythonService.modifyModelProportion(proportionDTO.getModelPath(), proportionDTO.getStretch(), name, proportionDTO.getTop(), proportionDTO.getBottom());
//        List<Integer> imagesWidthAndHeight = minioUtil.getImagesWidthAndHeight(modifiedModel);

        /*// 存储修改后的模特到个人library
        model = new Library();
        model.setAccountId(accountId);
        model.setLevel1Type(LibraryLevel1TypeEnum.MODELS.getRealName());
        model.setLevel2Type(gender);
        model.setName(uuid);
        model.setUrl(modifiedModel);
        model.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(modifiedModel, 24 * 60),false));
        model.setWidth(imagesWidthAndHeight.get(0));
        model.setHigh(imagesWidthAndHeight.get(1));
        model.setCreateDate(new Date());
        libraryService.save(model);*/

/*        // 新建模特点位信息
        LibraryModelPoint libraryModelPoint = new LibraryModelPoint();
        libraryModelPoint.setModelType("Library");
        libraryModelPoint.setRelationId(model.getId());
        libraryModelPoint.setShoulderLeft(Arrays.toString(proportionDTO.getShoulderLeft()));
        libraryModelPoint.setShoulderRight(Arrays.toString(proportionDTO.getShoulderRight()));
        libraryModelPoint.setWaistbandLeft(Arrays.toString(proportionDTO.getWaistbandLeft()));
        libraryModelPoint.setWaistbandRight(Arrays.toString(proportionDTO.getWaistbandRight()));
        libraryModelPoint.setHandLeft(Arrays.toString(proportionDTO.getHandLeft()));
        libraryModelPoint.setHandRight(Arrays.toString(proportionDTO.getHandRight()));
        libraryModelPoint.setCreateDate(new Date());
        libraryModelPointService.save(libraryModelPoint);*/
        return minioUtil.getPreSignedUrl(modifiedModel, CommonConstant.MINIO_IMAGE_EXPIRE_TIME);
    }

    /**
     * String collagePicture(Base64)
     * List<DTO(id, type)> elements
     * File file
     *
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public GenerateResultVO sketchReconstructionGenerate(SketchReconstructionDTO sketchReconstructionDTO) {
//        log.info("sketchReconstructionGenerate params: {}", sketchReconstructionDTO);

        Long accountId = UserContext.getUserHolder().getId();
        // 1、线稿生成
        String collagePictureBase64 = sketchReconstructionDTO.getCollagePicture();
        String path = accountId + "/CollagePicture/" + UUID.randomUUID();
        String minioPath = minioUtil.base64UploadToPath(collagePictureBase64, userBucket, path);

        Long projectId = sketchReconstructionDTO.getProjectId();

        GenerateResultVO generateResultVO = imageToSketch(new ImageToSketchDTO(null, "2", sketchReconstructionDTO.getGender()), minioPath, projectId);
        QueryWrapper<SketchReconstruction> qw = new QueryWrapper<>();
        qw.eq("project_id", projectId);
        SketchReconstruction sketchReconstruction = sketchReconstructionMapper.selectOne(qw);

        String url = generateResultVO.getUrl();
        // 找到路径的起始位置（从"://"之后查找第一个"/"）
        int pathStartIndex = url.indexOf("/", url.indexOf("://") + 3);
        // 找到查询参数的起始位置（"?" 的位置）
        int queryStartIndex = url.indexOf("?");
        // 截取目标部分
        String targetPath = url.substring(pathStartIndex + 1, queryStartIndex);

        try {
            if (Objects.isNull(sketchReconstruction)) {
                sketchReconstruction = new SketchReconstruction();
                sketchReconstruction.setProjectId(projectId);
                sketchReconstruction.setCollageImgSketchUrl(targetPath);
                sketchReconstruction.setGenerateDetailId(generateResultVO.getId());
                sketchReconstruction.setGender(sketchReconstructionDTO.getGender());
                sketchReconstruction.setCreateTime(LocalDateTime.now());
                sketchReconstructionMapper.insert(sketchReconstruction);
            } else {
                sketchReconstruction.setCollageImgSketchUrl(targetPath);
                sketchReconstruction.setGenerateDetailId(generateResultVO.getId());
                sketchReconstructionMapper.updateById(sketchReconstruction);
            }
        } catch (DuplicateKeyException e) {
            // 如果发生唯一键冲突，说明其他请求已经创建了记录
            // 重新查询并更新
            log.info("sketch拼贴，唯一键（project_id）冲突，改为更新");
            sketchReconstruction = sketchReconstructionMapper.selectOne(qw);
            sketchReconstruction.setCollageImgSketchUrl(targetPath);
            sketchReconstruction.setGenerateDetailId(generateResultVO.getId());
            sketchReconstructionMapper.updateById(sketchReconstruction);
        }

        return generateResultVO;
    }

    public SketchReconstructionVO getSketchReconstruction(Long projectId) {
        QueryWrapper<SketchReconstruction> qw = new QueryWrapper<>();
        qw.eq("project_id", projectId);
        SketchReconstruction sketchReconstruction = sketchReconstructionMapper.selectOne(qw);

        SketchReconstructionVO vo = new SketchReconstructionVO();
        if (Objects.nonNull(sketchReconstruction) && Objects.nonNull(sketchReconstruction.getGenerateDetailId())) {
            GenerateDetail generateDetail = generateDetailMapper.selectById(sketchReconstruction.getGenerateDetailId());
            vo.setCollageSketchUrl(minioUtil.getPreSignedUrl(generateDetail.getUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
            vo.setLiked(generateDetail.getIsLike().equals((byte) 1));
            String clothCategory = pythonService.getClothCategory(generateDetail.getUrl(), sketchReconstruction.getGender());
            String messageFromResource = BusinessException.getMessageFromResource(clothCategory.toUpperCase());
            vo.setCategory(clothCategory);
            vo.setCategoryValue(messageFromResource);
        }
        List<CollectionElement> collectionElements = collectionElementService.getByProjectId(projectId);
        if (!collectionElements.isEmpty()) {
            collectionElements.forEach(item -> {
                item.setUrl(StringUtil.isNullOrEmpty(item.getUrl()) ? null : minioUtil.getPreSignedUrl(item.getUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
            });
            vo.setUploadImages(collectionElements);
        } else {
            vo.setUploadImages(new ArrayList<>());
        }
        return vo;
    }

    public List<Map<String, String>> getAllPose() {
        List<Map<String, String>> propertyList = PoseEnum.getPropertyList();
        propertyList.forEach(item -> {
//            item.put("gif", minioUtil.getPreSignedUrl(item.get("gif"), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
            item.put("video", minioUtil.getPreSignedUrl(item.get("video"), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
            item.put("firstFrame", minioUtil.getPreSignedUrl(item.get("firstFrame"), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
        });
        return propertyList;
    }

    @Override
    @Transactional
    public void processPoseTransformResultBatch(String taskId, String gifUrl, String videoUrl, String imageUrl, String progress) {
        // 1、存储模型返回的数据
        PoseTransformation poseTransformation = getPoseTransformationByTaskId(taskId);
        if (Objects.isNull(poseTransformation)) {
            return;
        }

        poseTransformation.setGifUrl(gifUrl);
        poseTransformation.setVideoUrl(videoUrl);
        poseTransformation.setFirstFrameUrl(imageUrl);
        poseTransformation.setTaskStatus("Success");
        poseTransformation.setUpdateTime(LocalDateTime.now());
        poseTransformationMapper.updateById(poseTransformation);

        String taskIdBatch = poseTransformation.getTaskIdBatch();
        QueryWrapper<CloudTask> cloudTaskQueryWrapper = new QueryWrapper<>();
        cloudTaskQueryWrapper.lambda().eq(CloudTask::getTaskId, taskIdBatch);
        CloudTask cloudTask = cloudTaskMapper.selectOne(cloudTaskQueryWrapper);
        if (Objects.nonNull(cloudTask)) {
            cloudTask.setUpdateTime(LocalDateTime.now());
            cloudTaskMapper.updateById(cloudTask);
        }
//        if (Objects.nonNull(cloudTask)) {
//            if (cloudTask.getCompletedNum() == null) {
//                cloudTask.setCompletedNum(1);
//            }else {
//                cloudTask.setCompletedNum(cloudTask.getCompletedNum() + 1);
//            }
//            cloudTaskMapper.updateById(cloudTask);
//        }
        cloudTaskMapper.increaseCompletedNum(taskIdBatch);

        String key = generateResultKey + ":" + taskId;
        PoseTransformationVO poseTransformationVO = new PoseTransformationVO(
                poseTransformation.getId(), taskId, gifUrl, videoUrl, imageUrl, (byte) 0, "Success");

        // 2、更新redis
        redisUtil.addToString(key, new Gson().toJson(poseTransformationVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

        /*// 3、执行积分扣除
        String accountId = taskId.substring(taskId.lastIndexOf("-") + 1);
        String uuid = taskId.substring(0, taskId.lastIndexOf("-"));
        Boolean flag = creditsService.taskCreditsDeduction(Long.parseLong(accountId), taskIdBatch);
        if (flag) creditsService.updateChangedCredits(accountId, taskIdBatch);*/
    }

    @Transactional
    public void processPoseTransformResultBatch(String progress, String taskId) {
        // 1、存储模型返回的数据
        PoseTransformation poseTransformation = getPoseTransformationByTaskId(taskId);
        if (Objects.isNull(poseTransformation)) {
            return;
        }

        String taskIdBatch = poseTransformation.getTaskIdBatch();
        log.info("progress:{}", progress);
        log.info("taskIdBatch:{}", taskIdBatch);
        if (progress.equals("OK")) {
            if (!StringUtils.isEmpty(taskIdBatch)) {
                cloudTaskService.completeTask(taskIdBatch);
            }
        } else if (progress.startsWith("0/")) {
            if (!StringUtils.isEmpty(taskIdBatch)) {
                cloudTaskService.startTask(taskIdBatch);
            }
        }
    }


    @Transactional(rollbackFor = Exception.class)
    public void deleteGeneratedPose(Long projectId, Long id) {
        // 1. 权限校验
        Long accountId = UserContext.getUserHolder().getId();
        Project project = projectService.getById(projectId);
        if (!project.getAccountId().equals(accountId)) {
            throw new IllegalArgumentException("项目不属于当前账号");
        }

        // 2. 软删除主表数据
        int update = poseTransformationMapper.update(null,
                new UpdateWrapper<PoseTransformation>()
                        .eq("id", id)
                        .set("is_deleted", 1)
                        .set("update_time", LocalDateTime.now()));

        log.info("删除PoseTransfer 结果, id为{}, 影响行数={}", id, update);

        if (update == 0) return;

        // 3. 查询可能删除的多个排序项（存在脏数据可能，所有会查出多个）
        List<CollectionSort> deletedItems = collectionSortMapper.selectList(
                new QueryWrapper<CollectionSort>()
                        .eq("project_id", projectId)
                        .eq("relation_id", id)
                        .eq("relation_type", "PoseTransfer")
                        .orderByAsc("sort")
        );

        if (deletedItems.isEmpty()) return;

        // 4. 删除这些排序记录
        int deletedCount = collectionSortMapper.delete(
                new QueryWrapper<CollectionSort>()
                        .eq("project_id", projectId)
                        .eq("relation_id", id)
                        .eq("relation_type", "PoseTransfer")
        );

        // 5. 重新调整剩余记录的排序（两种方案可选）
        // 方案一：精确调整（每条被删除记录单独处理）
        for (CollectionSort deletedItem : deletedItems) {
            collectionSortMapper.update(
                    null,
                    new UpdateWrapper<CollectionSort>()
                            .eq("project_id", projectId)
                            .gt("sort", deletedItem.getSort())
                            .setSql("sort = sort - 1")
            );
        }

        log.info("删除PoseTransfer排序记录：id={}, 删除{}条，已重新排序后续记录", id, deletedCount);
    }


    /**
     * 万象专业版
     * 1、MoodBoard t2i
     * 2、PrintBoard t2i
     * 3、SketchBoard t2i
     * 4、pose transfer  图生舞蹈视频-舞动人像AnimateAnyone
     */

    /**
     * 创建异步任务
     *
     * @return taskId
     */
    public String createAsyncTask(GenerateThroughImageTextDTO generateDTO) {
//        String prompt = "一间有着精致窗户的花店，漂亮的木质门，摆放着花朵";
        String level1Type = generateDTO.getLevel1Type();
        String level2Type = generateDTO.getLevel2Type();
        String prompt = generateDTO.getText();
        Long userId = generateDTO.getUserId();
        String gender = generateDTO.getGender();

        // 添加预设prompt,使生成结果更加具有指向性（区分不同的board）
        switch (level1Type) {
            case "Moodboard":
                break;
            case "Printboard":
                prompt = "pattern image, " + prompt;
                break;
            case "Sketchboard":
                prompt = "a single item of sketch of " + prompt + ", clean white background, simple lines";
                break;
            default:
                log.warn("未知类型  type:{}", level1Type);
        }
        HashMap<String, Boolean> promptExtend = new HashMap<>();
        promptExtend.put("prompt_extend", false);
        ImageSynthesisParam param =
                ImageSynthesisParam.builder()
                        .apiKey(ALIYUN_API_KEY)
                        .model("wanx2.1-t2i-plus")
                        .prompt(prompt)
                        .n(1)
//                        .size("1024*1024")  采用默认值；1328*1328（默认值）：1:1。
                        .parameters(promptExtend)
                        .build();

        log.info(param.toString());

        ImageSynthesis imageSynthesis = new ImageSynthesis();
        ImageSynthesisResult result = null;
        try {
            result = imageSynthesis.asyncCall(param);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
        String taskId = result.getOutput().getTaskId();
        log.info("wx text2image 请求生成:{}, taskId：{}", JsonUtils.toJson(result), taskId);

        Generate generate = new Generate(userId, taskId, level1Type, level2Type, prompt, "text(" + gender + ")", "wx", new Date());
        save(generate);
        return taskId;
    }

    /**
     * 获取异步任务结果
     *
     * @param taskId 任务id
     */
    public GenerateResultVO getAsyncTaskResult(String taskId) {
        ImageSynthesis imageSynthesis = new ImageSynthesis();
        ImageSynthesisResult result = null;
        try {
            //如果已经在环境变量中设置了 DASHSCOPE_API_KEY，wait()方法可将apiKey设置为null
            result = imageSynthesis.fetch(taskId, ALIYUN_API_KEY);
            log.info(JsonUtils.toJson(result));
            //PENDING：任务排队中; RUNNING：任务处理中; SUCCEEDED：任务执行成功; FAILED：任务执行失败; CANCELED：任务取消成功; UNKNOWN：任务不存在或状态未知
            String taskStatus = result.getOutput().getTaskStatus();

            if (taskStatus.equals("SUCCEEDED")) {
                List<Generate> generates = selectListByUniqueId(taskId);
                String url = result.getOutput().getResults().get(0).get("url");

                String path = null;
                if (!generates.isEmpty()) {
                    Generate generate = generates.get(0);
                    Long accountId = generate.getAccountId();

                    // 1、下载图片
//                    InputStream inputStream = downloadImageFromAliyun(url);
                    byte[] bytes = downloadVideoOrImage(url);
                    // 2、上传图片到minio保存
                    String objectName = accountId + "/" + generate.getLevel1Type().toLowerCase() + "/" + taskId + ".png";
                    minioUtil.uploadToMinio(bytes, userBucket, objectName, "image/png");
                    path = userBucket + "/" + objectName;
                    // 3、生成结果保存到db
                    GenerateDetail generateDetail = new GenerateDetail();
                    generateDetail.setGenerateId(generate.getId());
                    generateDetail.setUrl(path);
                    generateDetail.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(path, 24 * 60), false));
                    generateDetail.setCreateDate(LocalDateTime.now());
                    generateDetailMapper.insert(generateDetail);
                    // 4、扣积分
                    Boolean flag = creditsService.taskCreditsDeduction(accountId, taskId);
                    if (flag) creditsService.updateChangedCredits(String.valueOf(generate.getAccountId()), taskId);

                    GenerateResultVO generateResultVO = new GenerateResultVO(taskId, generateDetail.getId(), minioUtil.getPreSignedUrl(path, 24 * 60), "Success");
                    if (generate.getLevel1Type().equals(SKETCH_BOARD.getRealName())) {
                        String gender = extractGender(generate.getGenerateType());
                        if (!StringUtil.isNullOrEmpty(gender)) {
                            String clothCategory = pythonService.getClothCategory(path, gender);
                            generateResultVO.setCategory(clothCategory);
                        } else {
                            log.warn("未提取到性别");
                        }
                    } else if (generate.getLevel1Type().equals(PRINT_BOARD.getRealName())) {
                        Generate generateRecord = selectByUniqueId(taskId);
                        generateResultVO.setCategory(generateRecord.getLevel2Type());
                    }
                    return generateResultVO;
                } else {
                    throw new BusinessException("Unknown generate task");
                }
            } else if (taskStatus.equals("PENDING") || taskStatus.equals("RUNNING")) {
                log.info("阿里 异步接口返回生成状态为：{}", taskStatus);
                return new GenerateResultVO(taskId, null, null, "Executing");
            } else {
                log.warn("阿里 异步接口返回生成状态为：{}", taskStatus);
                return new GenerateResultVO(taskId, null, null, "Fail");
            }
        } catch (ApiException | NoApiKeyException e) {
            throw new RuntimeException(e.getMessage());
        } catch (Exception e) {
            log.error("从aliyun下载图片失败, {}", e.getMessage());
            throw new BusinessException("Generation result retrieval failed");
        }
    }

    public GenerateResultVO getGoogleAsyncResult(String taskId) {
        // 1. 从 Redis 获取当前任务状态
        String key = generateResultKey + ":" + taskId;
        String redisResult = redisUtil.getFromString(key);

        if (StringUtil.isNullOrEmpty(redisResult)) {
            // Redis 中没有该任务记录，可能任务已过期或 taskId 无效
            return new GenerateResultVO(taskId, null, null, "Unknown");
        }

        GenerateResultVO cachedResult = new Gson().fromJson(redisResult, GenerateResultVO.class);

        // 2. 如果任务还在执行中，直接返回当前状态
        if ("Executing".equals(cachedResult.getStatus())) {
            // 这里可以添加额外的检查逻辑，比如检查是否执行超时
            // 如果需要，可以从 Doubao API 查询实际状态
            return cachedResult;
        }

        // 3. 如果任务已经完成（成功或失败），直接返回结果
        if ("Success".equals(cachedResult.getStatus())) {
            List<Generate> generates = selectListByUniqueId(taskId);
            String url = cachedResult.getUrl();

            String path = null;
            if (!generates.isEmpty()) {
                Generate generate = generates.get(0);
                Long accountId = generate.getAccountId();


                // 3、生成结果保存到db
                GenerateDetail generateDetail = new GenerateDetail();
                generateDetail.setGenerateId(generate.getId());
                generateDetail.setUrl(url);
                generateDetail.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(url, 24 * 60), false));
                generateDetail.setCreateDate(LocalDateTime.now());
                generateDetailMapper.insert(generateDetail);
                // 4、扣积分
                Boolean flag = creditsService.taskCreditsDeduction(accountId, taskId);
                if (flag) creditsService.updateChangedCredits(String.valueOf(generate.getAccountId()), taskId);

                GenerateResultVO generateResultVO = new GenerateResultVO(taskId, generateDetail.getId(), minioUtil.getPreSignedUrl(url, 24 * 60), "Success");
                if (generate.getLevel1Type().equals(SKETCH_BOARD.getRealName())) {
                    String gender = extractGender(generate.getGenerateType());
                    if (!StringUtil.isNullOrEmpty(gender)) {
                        String clothCategory = pythonService.getClothCategory(url, gender);
                        generateResultVO.setCategory(clothCategory);
                    } else {
                        log.warn("未提取到性别");
                    }
                } else if (generate.getLevel1Type().equals(PRINT_BOARD.getRealName())) {
                    Generate generateRecord = selectByUniqueId(taskId);
                    generateResultVO.setCategory(generateRecord.getLevel2Type());
                }
                return generateResultVO;
            } else if ("Fail".equals(cachedResult.getStatus())) {
                // 获取失败原因
                String errorMessage = cachedResult.getUrl();
                if (errorMessage != null) {
                    throw new BusinessException(errorMessage, ResultEnum.PROMPT.getCode());
                }
            } else {
                throw new BusinessException("Unknown generate task");
            }
        }

        // 4. 其他状态（如 Unknown），返回默认失败
        return new GenerateResultVO(taskId, null, null, "Fail");
    }

    public GenerateResultVO getDoubaoAsyncResult(String taskId) {
        // 1. 从 Redis 获取当前任务状态
        String key = generateResultKey + ":" + taskId;
        String redisResult = redisUtil.getFromString(key);

        if (StringUtil.isNullOrEmpty(redisResult)) {
            // Redis 中没有该任务记录，可能任务已过期或 taskId 无效
            return new GenerateResultVO(taskId, null, null, "Unknown");
        }

        GenerateResultVO cachedResult = new Gson().fromJson(redisResult, GenerateResultVO.class);

        // 2. 如果任务还在执行中，直接返回当前状态
        if ("Executing".equals(cachedResult.getStatus())) {
            // 这里可以添加额外的检查逻辑，比如检查是否执行超时
            // 如果需要，可以从 Doubao API 查询实际状态
            return cachedResult;
        }

        // 3. 如果任务已经完成（成功或失败），直接返回结果
        if ("Success".equals(cachedResult.getStatus())) {
            List<Generate> generates = selectListByUniqueId(taskId);
            String url = cachedResult.getUrl();

            String path = null;
            if (!generates.isEmpty()) {
                Generate generate = generates.get(0);
                Long accountId = generate.getAccountId();

                // 1、下载图片
//                    InputStream inputStream = downloadImageFromAliyun(url);
                byte[] bytes = downloadVideoOrImage(url);
                // 2、上传图片到minio保存
                String objectName = accountId + "/" + generate.getLevel1Type().toLowerCase() + "/" + taskId + ".png";
                minioUtil.uploadToMinio(bytes, userBucket, objectName, "image/png");
                path = userBucket + "/" + objectName;
                // 3、生成结果保存到db
                GenerateDetail generateDetail = new GenerateDetail();
                generateDetail.setGenerateId(generate.getId());
                generateDetail.setUrl(path);
                generateDetail.setMd5(MD5Utils.encryptFile(minioUtil.getPreSignedUrl(path, 24 * 60), false));
                generateDetail.setCreateDate(LocalDateTime.now());
                generateDetailMapper.insert(generateDetail);
                // 4、扣积分
                Boolean flag = creditsService.taskCreditsDeduction(accountId, taskId);
                if (flag) creditsService.updateChangedCredits(String.valueOf(generate.getAccountId()), taskId);

                GenerateResultVO generateResultVO = new GenerateResultVO(taskId, generateDetail.getId(), minioUtil.getPreSignedUrl(path, 24 * 60), "Success");
                if (generate.getLevel1Type().equals(SKETCH_BOARD.getRealName())) {
                    String gender = extractGender(generate.getGenerateType());
                    if (!StringUtil.isNullOrEmpty(gender)) {
                        String clothCategory = pythonService.getClothCategory(path, gender);
                        generateResultVO.setCategory(clothCategory);
                    } else {
                        log.warn("未提取到性别");
                    }
                } else if (generate.getLevel1Type().equals(PRINT_BOARD.getRealName())) {
                    Generate generateRecord = selectByUniqueId(taskId);
                    generateResultVO.setCategory(generateRecord.getLevel2Type());
                }
                return generateResultVO;
            } else {
                throw new BusinessException("Unknown generate task");
            }
        } else if ("Fail".equals(cachedResult.getStatus())) {
            // 获取失败原因
            String errorMessage = cachedResult.getUrl();
            if (errorMessage != null) {
                throw new BusinessException(errorMessage, ResultEnum.PROMPT.getCode());
            }
        }

        // 4. 其他状态（如 Unknown），返回默认失败
        return new GenerateResultVO(taskId, null, null, "Fail");
    }

    private static final String IMAGE_DETECT = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/aa-detect";
    private static final String TEMPLATE_ID_GEN = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/aa-template-generation/";
    private static final String GET_ASYNC_RESULT = "https://dashscope.aliyuncs.com/api/v1/tasks/";
    private static final String ANIMATE = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2video/video-synthesis/";

    public String animateAnyone(PoseTransformDTO poseTransformDTO, Long accountId) {
        AccessLimitUtils.validate("animation", 5);
        String inputImage = poseTransformDTO.getProductImage();
        String inputImageUrl = minioUtil.getPreSignedUrl(inputImage, CommonConstant.MINIO_IMAGE_EXPIRE_TIME);
        // 1、输入图片检测
        checkImage(inputImageUrl);

        // 2、动作模板生成
        String videoTemplateId = PoseEnum.getById(poseTransformDTO.getPoseId()).getTemplateId();
        if (StringUtil.isNullOrEmpty(videoTemplateId)) {
            throw new BusinessException("unknown pose");
        }
        // 3、生成动图
        JSONObject requestBody1 = new JSONObject();
        requestBody1.set("model", "animate-anyone-gen2");

        JSONObject input1 = new JSONObject();
        input1.set("image_url", inputImageUrl); // 替换为实际图片URL
        input1.set("template_id", videoTemplateId); // 替换为实际图片URL
        JSONObject parameters1 = new JSONObject();
        parameters1.set("use_ref_img_bg", false);
        parameters1.set("video_ratio", "9:16");

        requestBody1.set("input", input1);
        requestBody1.set("parameters", parameters1);

        log.info("万象 pose transfer 请求入参：{}", requestBody1);
        String resp = sendRequestUtil.sendAliYunPostAsync(ANIMATE, requestBody1.toString());
//        String resp = "{\"request_id\":\"656c4339-59e5-9b34-a010-b5aa625a4008\",\"output\":{\"task_id\":\"05c0fe3e-8d93-4754-babe-28a1efc62151\",\"task_status\":\"PENDING\"}}";
        log.info("wx pose transform 请求生成，获取taskId:{}", resp);
        JSONObject jsonResponse = JSONUtil.parseObj(resp);
        JSONObject output = jsonResponse.getJSONObject("output");
        String status = output.getStr("task_status");
        if (status.equals(FAILED.getName()) || status.equals(UNKNOWN_W.getName())) {
            return null;
        }
        return output.getStr("task_id");
    }

    public void checkImage(String inputImageUrl) {
        JSONObject requestBody = new JSONObject();
        requestBody.set("model", "animate-anyone-detect-gen2");

        JSONObject input = new JSONObject();
        input.set("image_url", inputImageUrl); // 替换为实际图片URL
        JSONObject parameters = new JSONObject();

        requestBody.set("input", input);
        requestBody.set("parameters", parameters);

        String response = sendRequestUtil.sendAliYunPost(IMAGE_DETECT, requestBody.toString());

        System.out.println("API响应: " + response);
        JSONObject jsonResponse = JSONUtil.parseObj(response);
        // 获取check_pass值
        JSONObject output = jsonResponse.getJSONObject("output");
        Boolean checkPass = output.getBool("check_pass");

        if (!checkPass) {
            String reason = output.getStr("reason");
            log.info("原因: {}", reason);
            throw new BusinessException("输入的图片不满足要求");
        }
    }

    // 轮询配置
    private static final int MAX_RETRIES = 30; // 最大重试次数
    private static final int POLL_INTERVAL = 20000; // 轮询间隔(毫秒)

    public String getVideoTemplateId(String videoPath) {
        boolean contains = PoseEnum.getVideoList().contains(videoPath);

        String templateId;
        if (!contains) {
            String videoUrl = minioUtil.getPreSignedUrl(videoPath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME);
            JSONObject requestBody = new JSONObject();
            requestBody.set("model", "animate-anyone-template-gen2");
            JSONObject input = new JSONObject();
            input.set("video_url", videoUrl); // 替换为实际图片URL
            JSONObject parameters = new JSONObject();
            requestBody.set("input", input);
            requestBody.set("parameters", parameters);

            log.info("获取pose的模板id 请求数据：{}", requestBody);
            String resp = sendRequestUtil.sendAliYunPostAsync(TEMPLATE_ID_GEN, requestBody.toString());
            if (StringUtil.isNullOrEmpty(resp)) {
                throw new BusinessException("请求获取video template id失败");
            }
            JSONObject jsonResponse = JSONUtil.parseObj(resp);
            log.info("getVideoTemplateId response:{}", jsonResponse);
            JSONObject output = jsonResponse.getJSONObject("output");
            String taskId = output.getStr("task_id");

            // 暂时用while循环轮询
            templateId = pollTemplateIdResult(taskId);
            if (StringUtil.isNullOrEmpty(templateId)) {
                throw new BusinessException("获取动作模板失败");
            }
//            templateId = "AACT.8090e67b.-E3pujumEfCbDTI_rjSH-A.LwIlGT3j";
        } else {
            templateId = PoseEnum.getByVideoPath(videoPath).getTemplateId();
        }

        return templateId;
    }

    public String pollTemplateIdResult(String taskId) {
        int attempt = 0;
        boolean isCompleted = false;
        String templateId = null;

        while (attempt < MAX_RETRIES && !isCompleted) {
            attempt++;
            System.out.printf("尝试第 %d 次查询...%n", attempt);

            try {
                // 发送GET请求查询任务状态
                HttpResponse httpResponse = HttpRequest.get(GET_ASYNC_RESULT + taskId)
                        .header(Header.AUTHORIZATION, "Bearer " + ALIYUN_API_KEY)
                        .timeout(10000)
                        .execute();

                if (httpResponse.getStatus() == 200) {
                    JSONObject response = JSONUtil.parseObj(httpResponse.body());
                    JSONObject output = JSONUtil.parseObj(response.getStr("output"));
                    String taskStatus = output.getStr("task_status", "UNKNOWN");
                    WangXiangTaskStatusEnum statusEnum = WangXiangTaskStatusEnum.fromName(taskStatus);
                    System.out.println("当前任务状态: " + taskStatus);

                    switch (statusEnum) {
                        case SUCCEEDED:
                            templateId = handleSuccessResponse(response);
                            isCompleted = true;
                            break;
                        case FAILED:
                        case UNKNOWN_W:
                            handleFailedResponse(response);
                            isCompleted = true;
                            break;
                        case RUNNING:
                        case PENDING_W:
                            // 任务仍在运行，继续等待
                            break;
                        default:
                            System.out.println("未知状态: " + taskStatus);
                    }
                } else {
                    System.out.println("请求失败，状态码: " + httpResponse.getStatus());
                }

                // 如果不是最终状态，等待一段时间再重试
                if (!isCompleted && attempt < MAX_RETRIES) {
                    TimeUnit.MILLISECONDS.sleep(POLL_INTERVAL);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                System.out.println("轮询被中断");
                break;
            } catch (Exception e) {
                System.out.println("请求发生异常: " + e.getMessage());
                // 发生异常时可以选择重试或退出
                break;
            }
        }
        if (!isCompleted) {
            System.out.println("达到最大重试次数仍未获取最终结果");
        }
        return templateId;
    }

    private static String handleSuccessResponse(JSONObject response) {
        log.info("任务执行成功!");
        // 提取成功结果
        JSONObject output = response.getJSONObject("output");
        if (output != null) {
            log.info("任务输出: {}", output.toStringPretty());
            return output.getStr("template_id");
        } else {
            return null;
        }
    }

    private static void handleFailedResponse(JSONObject response) {
        log.info("任务执行失败!");
        // 提取失败原因
        String errorMsg = response.getStr("error_message", "未知错误");
        log.info("失败原因: {}", errorMsg);
    }


    public PoseTransformationVO getAnimateResult(String taskId) {
        String fullUrl = GET_ASYNC_RESULT + taskId;
        // 从接口获取当前任务的结果
        String respBody = sendRequestUtil.sendAliYunGet(fullUrl);
        log.info("获取wx pose transform 的结果: {}", respBody);

        String outputStr = JSONUtil.parseObj(respBody).getStr("output");
        JSONObject output = JSONUtil.parseObj(outputStr);
        String videoUrl = output.getStr("video_url");
        String status = output.getStr("task_status");

        // 更新api_generate表
        apiGenerateService.updateAPIGenerateStatusAsync(taskId, status);

        List<PoseTransformation> poseTransformations = poseTransformationMapper.selectList(new QueryWrapper<PoseTransformation>().eq("unique_id", taskId).orderByDesc("id"));
        PoseTransformation poseTransformation;
        if (!poseTransformations.isEmpty()) {
            poseTransformation = poseTransformations.get(0);
        } else {
            throw new BusinessException("unknown motion task");
        }
        Long accountId = poseTransformation.getAccountId();
        PoseTransformationVO poseTransformationVO = new PoseTransformationVO();
        WangXiangTaskStatusEnum statusEnum = WangXiangTaskStatusEnum.fromName(status);
        switch (statusEnum) {
            case SUCCEEDED:
                AccessLimitUtils.validateOut("animation");
                poseTransformationVO = CopyUtil.copyObject(poseTransformation, PoseTransformationVO.class);
                poseTransformationVO.setStatus("Success");

                // 生成视频的gif和第一帧图片并上传图片、视频、gif等数据，更新pose transformation表
                processVideo(videoUrl, poseTransformation);
                poseTransformationVO.setId(poseTransformation.getId());
                if (!StringUtil.isNullOrEmpty(poseTransformation.getGifUrl())) {
                    poseTransformationVO.setGifUrl(minioUtil.getPreSignedUrl(poseTransformation.getGifUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
                }
                if (!StringUtil.isNullOrEmpty(poseTransformation.getVideoUrl())) {
                    poseTransformationVO.setVideoUrl(minioUtil.getPreSignedUrl(poseTransformation.getVideoUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
                }
                if (!StringUtil.isNullOrEmpty(poseTransformation.getFirstFrameUrl())) {
                    poseTransformationVO.setFirstFrameUrl(minioUtil.getPreSignedUrl(poseTransformation.getFirstFrameUrl(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
                }
                // 执行积分扣除
                Boolean flag = creditsService.taskCreditsDeduction(accountId, taskId);
                if (flag) creditsService.updateChangedCredits(String.valueOf(accountId), taskId);

                // 保存数据到redis
                String key = generateResultKey + ":" + taskId;
                redisUtil.addToString(key, new Gson().toJson(poseTransformationVO), CommonConstant.GENERATE_RESULT_EXPIRE_TIME);
                break;
            case FAILED:
                AccessLimitUtils.validateOut("animation");
                updatePoseTransferStatus(taskId, "Fail", poseTransformation);
                // 将积分暂扣区的积分移除
                creditsService.deleteCreditsDeduction(accountId, taskId);
                throw new BusinessException(output.getStr("message"), ResultEnum.PROMPT.getCode());
            case UNKNOWN_W:
                AccessLimitUtils.validateOut("animation");
                poseTransformationVO.setStatus("Fail");
                updatePoseTransferStatus(taskId, "Fail", poseTransformation);
                // 将积分暂扣区的积分移除
                creditsService.deleteCreditsDeduction(accountId, taskId);
                break;
            case RUNNING:
            case PENDING_W:
                // 任务仍在运行，继续等待
                poseTransformationVO.setStatus("Executing");
                break;
            default:
                AccessLimitUtils.validateOut("animation");
                log.info("未知状态: {}", status);
                poseTransformationVO.setStatus("Fail");
                updatePoseTransferStatus(taskId, "Fail", poseTransformation);
                // 将积分暂扣区的积分移除
                creditsService.deleteCreditsDeduction(accountId, taskId);
        }
        poseTransformationVO.setTaskId(taskId);
        // 发送提示消息
        sendSysMsgForPT(poseTransformation);

        return poseTransformationVO;
    }

    public void processVideo(String aliyunVideoUrl, PoseTransformation poseTransformation) /*throws Exception*/ {
        // 1. 从阿里云下载视频到内存
        byte[] videoBytes = downloadVideoOrImage(aliyunVideoUrl);
        Long accountId = poseTransformation.getAccountId();
        String taskId = poseTransformation.getUniqueId();

        // 2. 提取第一帧和生成GIF
        ByteArrayOutputStream firstFrameOutput = new ByteArrayOutputStream();
        ByteArrayOutputStream gifOutput = new ByteArrayOutputStream();

        try (FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(new ByteArrayInputStream(videoBytes))) {
            grabber.start();
            // 提取第一帧
            BufferedImage firstFrame = new Java2DFrameConverter().convert(grabber.grabImage());
            ImageIO.write(firstFrame, "jpg", firstFrameOutput);

            // 生成GIF（取前12秒，50帧）
            generateGif(grabber, gifOutput, 12, 60);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        // 3. 上传所有文件到MinIO
        String videoPrefix = accountId + "/pose_transform_video/" + taskId + ".mp4";
        String imgPrefix = accountId + "/pose_transform_first_img/" + taskId + ".jpg";
        String gifPrefix = accountId + "/pose_transform_gif/" + taskId + ".gif";

        minioUtil.uploadToMinio(videoBytes, userBucket, videoPrefix, "video/mp4");
        minioUtil.uploadToMinio(firstFrameOutput.toByteArray(), userBucket, imgPrefix, "image/jpeg");
        minioUtil.uploadToMinio(gifOutput.toByteArray(), userBucket, gifPrefix, "image/gif");
        // 存储数据到数据库
        poseTransformation.setGifUrl(userBucket + "/" + gifPrefix);
        poseTransformation.setVideoUrl(userBucket + "/" + videoPrefix);
        poseTransformation.setFirstFrameUrl(userBucket + "/" + imgPrefix);
        poseTransformation.setTaskStatus("Success");
        poseTransformation.setUpdateTime(LocalDateTime.now());
        poseTransformationMapper.updateById(poseTransformation);
    }

    public byte[] downloadVideoOrImage(String url) {
        try (CloseableHttpClient client = HttpClients.createDefault();
             InputStream in = client.execute(new HttpGet(url)).getEntity().getContent()) {
            return IOUtils.toByteArray(in);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 增强版下载方法 todo 最好不要报错
    private byte[] downloadVideoOrImageWithValidation(String url) throws IOException {
        CloseableHttpClient client = HttpClients.createDefault();
        HttpGet request = new HttpGet(url);

        try (CloseableHttpResponse response = client.execute(request)) {
            // 状态码检查
            if (response.getStatusLine().getStatusCode() != 200) {
                throw new IOException("Invalid status: " + response.getStatusLine());
            }

            // 内容类型检查
            org.apache.http.Header contentTypeHeader = response.getFirstHeader("Content-Type");
            if (contentTypeHeader == null || !contentTypeHeader.getValue().startsWith("image/")) {
                throw new IOException("Invalid content type: " +
                        (contentTypeHeader != null ? contentTypeHeader.getValue() : "null"));
            }

            // 内容长度检查
            org.apache.http.Header contentLengthHeader = response.getFirstHeader("Content-Length");
            if (contentLengthHeader != null) {
                long length = Long.parseLong(contentLengthHeader.getValue());
                if (length <= 0) {
                    throw new IOException("Empty content");
                }
            }

            return IOUtils.toByteArray(response.getEntity().getContent());
        }
    }

    public byte[] downloadWithProxy(String url) throws IOException {
        // 获取系统代理设置（适用于大多数VPN）
//        String proxyHost = System.getProperty("http.proxyHost");
//        String proxyPort = System.getProperty("http.proxyPort");
        String proxyHost = "localhost";
        String proxyPort = "7890";

        CloseableHttpClient client;
        if (proxyHost != null && proxyPort != null) {
            // 配置代理
            HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort));
            RequestConfig config = RequestConfig.custom().setProxy(proxy).build();
            client = HttpClients.custom().setDefaultRequestConfig(config).build();
        } else {
            client = HttpClients.createDefault();
        }

        try {
            return client.execute(new HttpGet(url), response -> {
                if (response.getStatusLine().getStatusCode() == 200) {
                    return IOUtils.toByteArray(response.getEntity().getContent());
                } else {
                    throw new IOException("HTTP Error: " + response.getStatusLine());
                }
            });
        } finally {
            client.close();
        }
    }

    public static void generateGif(FFmpegFrameGrabber grabber, OutputStream output,
                                   int durationSec, int frameCount) throws Exception {
        Java2DFrameConverter converter = new Java2DFrameConverter();
        AnimatedGifEncoder gifEncoder = new AnimatedGifEncoder();

        // 配置GIF参数
        gifEncoder.start(output);
        gifEncoder.setDelay(100); // 每帧延迟(毫秒)
        gifEncoder.setRepeat(0);  // 0=无限循环

        int totalFrames = (int) (grabber.getFrameRate() * durationSec);
        int step = Math.max(1, totalFrames / frameCount);

        // 逐帧处理
        for (int i = 0; i < totalFrames; i += step) {
            grabber.setVideoFrameNumber(i);
            BufferedImage frame = converter.convert(grabber.grabImage());
            if (frame != null) {
                gifEncoder.addFrame(frame);
            }
        }
        gifEncoder.finish();
    }

    /**
     * Freepik
     * To Product Image
     */
    public String reimagineFreePik(String path, String prompt, String style) throws IOException {
        String imageAsBase64 = minioUtil.getImageAsBase64(path);
        log.info(minioUtil.getPreSignedUrl(path, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
        JSONObject requestBody = new JSONObject();
        requestBody.set("image", imageAsBase64);
        requestBody.set("prompt", prompt);
        requestBody.set("imagination", style);

        String resp = sendRequestUtil.sendFreepikPost(requestBody.toString());
        if (!StringUtil.isNullOrEmpty(resp)) {
            JSONObject jsonResp = JSONUtil.parseObj(resp);
            JSONObject data = JSONUtil.parseObj(jsonResp.get("data"));
            String status = data.getStr("status");
            if (status.equals("COMPLETED")) {
//                List<String> generated = data.getBeanList("generated", String.class);
                log.info("freepik 调用结果：{}", jsonResp);
                return jsonResp.getStr("data");
            }
        }
        return null;
    }

    /**
     * imagePath 图片的minio地址
     * ollama
     * prompt 助手
     */
    public String getImageDescription(String imagePath) {
/*        // 1. 读取图片并编码为 Base64
        String imageAsBase64 = null;
        try {
            imageAsBase64 = minioUtil.getImageAsBase64(imagePath);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 2. 构建 JSON 请求体
        JSONObject message = new JSONObject();
        message.set("role", "user");
        message.set("content", "Please describe the clothing in the image and provide a line art description of the outfit. The description should allow for the reconstruction of the corresponding line art based on the details given.");
        message.set("images", JSONUtil.createArray().set(imageAsBase64));

        JSONObject requestBody = new JSONObject();
        requestBody.set("model", "llama3.2-vision");
        requestBody.set("messages", JSONUtil.createArray().set(message));
        requestBody.set("stream", false);*/

//        log.info("request body:{}", requestBody);
        JSONObject requestBody = new JSONObject();
        requestBody.set("img", imagePath);
//        String description = sendRequestUtil.sendPost("http://localhost:8000/api/img2prompt", requestBody.toString());
        String description = sendRequestUtil.sendPost("http://18.167.251.121:9994/api/img2prompt", requestBody.toString());
        if (StringUtil.isNullOrEmpty(description)) {
            throw new BusinessException("从ollama获取图片描述失败");
        }
        /*Object msg = JSONUtil.parseObj(resp).get("message");
        String description = JSONUtil.parseObj(msg).getStr("content");*/
        log.info("image :{} \n, description: {}",
                minioUtil.getPreSignedUrl(imagePath, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), description);
        return description;
    }

    private String resolveModelType(String taskId, String func) {
        // 判断当前task来自哪个模型
        if (!StringUtil.isNullOrEmpty(func)
                && func.equals(POSE_TRANSFORMATION.getName())) {
            List<PoseTransformation> poseTransformations = poseTransformationMapper.selectList(
                    new QueryWrapper<PoseTransformation>().eq("unique_id", taskId));
            if (!poseTransformations.isEmpty()
                    && !StringUtil.isNullOrEmpty(poseTransformations.get(0).getModelName())
                    && poseTransformations.get(0).getModelName().equals("wx")) {
                return "wx";
            } else {
                return "local";
            }
        }

        Generate generate = selectByUniqueId(taskId);
        if (Objects.nonNull(generate) &&
                !StringUtil.isNullOrEmpty(generate.getModelName()) &&
                (generate.getModelName().equals("wx")
                        || generate.getModelName().equals("freepik")
                        || generate.getModelName().equals("flux")
                        || generate.getModelName().contains("qwen")
                        || generate.getModelName().contains("imagen")
                        || generate.getModelName().contains("gemini")
                        || generate.getModelName().contains("doubao"))) {
            return generate.getModelName();
        } else {
            return "local";
        }
    }

    public static String extractGender(String text) {
        // 匹配末尾的 (Male) 或 (Female)，忽略大小写
        Pattern pattern = Pattern.compile("\\(([Mm]ale|[Ff]emale)\\)$");
        Matcher matcher = pattern.matcher(text);

        if (matcher.find()) {
            return matcher.group(1); // 返回括号内的内容
        }
        return null; // 未匹配到性别
    }

    /**
     * 接入flux模型，用于imageToSketch(sketch extract) || relighting || to product image
     *
     * @param func       功能枚举名，指定使用flux模型的具体功能类型
     * @param prompt     用户输入的提示词，如果为空则使用默认提示词
     * @param imagePath  图片minio路径，作为输入图像的base64编码源
     * @param childStyle 是否为儿童风格，影响提示词的构建
     * @return 返回taskId，用于异步获取结果
     */
    public String flux(CreditsEventsEnum func, String prompt, String imagePath, boolean childStyle) {
        // Flux API的请求地址
        String fluxRequestUrl = "https://api.bfl.ai/v1/flux-kontext-pro";

        // 如果用户没有提供提示词，根据功能类型设置默认提示词
        if (StringUtil.isNullOrEmpty(prompt)) {
            switch (func) {
                case RELIGHT_FLUX:
                    // 重新打光功能的默认提示词
                    prompt = "a model standing on the beautiful beach, ultra high quality, 8k";
                    break;
                case IMAGE_TO_SKETCH_FLUX:
                    // 图片转线稿功能的默认提示词
                    prompt = "generate the sketch of the image, simple line, ultra high quality";
                    break;
                case TO_PRODUCT_IMAGE_ADVANCED:
                    // 转产品图功能的默认提示词
                    prompt = "change the image to real style, ultra high quality, 8k";
                    // 如果是儿童风格，添加儿童面部特征描述
                    if (childStyle) prompt = prompt + ", Children's face";
                    break;
            }
        } else {
            // 如果用户提供了提示词，先进行提示词优化处理
            prompt = modifyPrompt(prompt, null, func.getName(), null);
            // 根据不同功能类型，为提示词添加特定前缀
            switch (func) {
                case PATTERN:
                    // 图案生成功能，添加图案前缀
                    prompt = "pattern image, " + prompt;
                    break;
                case SKETCH_BOARD:
                    // 线稿板功能，添加线稿描述
                    prompt = "a single item of sketch of " + prompt + ", clean white background, simple lines";
                    break;
            }
        }

        // 构建Flux API请求体
        JSONObject requestBody = new JSONObject();

        // 设置生成提示词
        requestBody.set("prompt", prompt);
        // 设置随机种子，确保结果的可重现性
        requestBody.set("seed", 42);
        // 根据功能类型设置图片宽高比
        if (func.equals(PATTERN)) {
            // 图案生成使用正方形比例
            requestBody.set("aspect_ratio", "1:1");
        } else {
            // 其他功能使用竖屏比例
            requestBody.set("aspect_ratio", "9:16");
        }
        // 设置输出格式为PNG
        requestBody.set("output_format", "png");

        log.info("flux 请求入参：{}", requestBody);
        // 提示词不能为空的校验
        if (prompt.isEmpty()) throw new BusinessException("test");

        // 如果提供了输入图片路径，需要将图片转换为base64格式
        if (!StringUtil.isNullOrEmpty(imagePath)) {
            try {
                String imageAsBase64 = null;
                // 对于转产品图功能，先添加白色背景处理
                if (func.equals(TO_PRODUCT_IMAGE_ADVANCED)) {
                    imageAsBase64 = addWhiteBackground(imagePath);
                }
                // 如果白色背景处理失败或不需要，直接获取原图的base64编码
                if (StringUtil.isNullOrEmpty(imageAsBase64)) {
                    imageAsBase64 = minioUtil.getImageAsBase64(imagePath);
                }
                // 将base64编码的图片添加到请求体中
                requestBody.set("input_image", imageAsBase64);
            } catch (IOException e) {
                log.error("获取图片的base64格式失败，{}", String.valueOf(e));
                throw new BusinessException("Failed to obtain the image in base64 format.");
            }
        }

        // 发送POST请求到Flux API
        String resp = sendRequestUtil.sendFluxPost(fluxRequestUrl, requestBody.toString());
        JSONObject respObj = JSONUtil.parseObj(resp);
        log.info("flux 发起生成请求返回结果： {}", respObj);

        // 从响应中提取任务ID
        String taskId = respObj.getStr("id");
        if (StringUtil.isNullOrEmpty(taskId)) {
            // 任务创建失败，记录错误信息并抛出异常
            requestBody.set("input_image", imagePath);
            log.error("flux生成任务创建失败，func ：{}， requestBody:{}", func.getName(), requestBody);
            throw new BusinessException("Failed to generate task. Please retry later.");
        }

        // 获取轮询URL，用于后续查询任务状态
        String pollingUrl = respObj.getStr("polling_url");
        String key = RedisUtil.FLUX_POLLING_URL + taskId;
        // 将轮询URL存储到Redis中，设置过期时间
        redisUtil.addToString(key, pollingUrl, CommonConstant.GENERATE_RESULT_EXPIRE_TIME);

        // 添加到api_generate表中，以便之后对结果查询做补偿机制
        apiGenerateService.addAPIGenerateRecordAsync(UserContext.getUserHolder().getId(), taskId, func.getName(), "flux", "Pending");

        // 返回任务ID，用于异步查询结果
        return taskId;
    }

    @Override
    public String getFluxResult(String taskId, String objectName) {
        // 获取轮询URL
        String pollingUrl = redisUtil.getFromString(RedisUtil.FLUX_POLLING_URL + taskId);

        // 准备请求参数
        String fluxResultRequestUrl = StringUtil.isNullOrEmpty(pollingUrl)
                ? "https://api.bfl.ai/v1/get_result"
                : pollingUrl;

        HashMap<String, Object> params = new HashMap<>();
        if (StringUtil.isNullOrEmpty(pollingUrl)) {
            params.put("id", taskId);
        }

        // 发送请求并解析响应
        String resp = sendRequestUtil.sendGet(fluxResultRequestUrl, params);
        log.info("获取flux生成的结果为：{}", resp);

        JSONObject respObj = JSONUtil.parseObj(resp);
        String status = respObj.getStr("status");
        FluxTaskStatusEnum statusEnum = FluxTaskStatusEnum.fromName(status);
        // 异步更新状态
        apiGenerateService.updateAPIGenerateStatusAsync(taskId, status);

        // 处理不同状态
        switch (statusEnum) {
            case TASK_NOT_FOUND:
            // 审核没过
            case REQUEST_MODERATED:
            // 审核没过
            case CONTENT_MODERATED:
            // 出错
            case ERROR:
                return "Fail";
            case PENDING_F:
                return "Pending";
            case SUCCESS:
                // 已完成 获取结果
                return handleReadyStatus(respObj, objectName);
            default:
                return null;
        }
    }

    private String handleReadyStatus(JSONObject respObj, String objectName) {
        // 1. 首先检查MinIO中是否已存在该图片
        if (minioUtil.doesObjectExist(userBucket, objectName)) {
            return userBucket + "/" + objectName;
        }

        // 2. 解析响应获取结果URL和生成时间
        JSONObject resultObj = JSONUtil.parseObj(respObj.getStr("result"));
        String fluxResult = resultObj.getStr("sample");
        double endTime = resultObj.getDouble("end_time"); // 获取任务结束时间戳

        // 3. 检查图片链接是否已过期（超过10分钟）
        long currentTime = System.currentTimeMillis() / 1000; // 当前Unix时间戳（秒）
        long generateTime = (long) endTime; // 生成结束时间戳
        // 图片10分钟过期，保险起见，保留一分钟
        long tenMinutesInSeconds = 9 * 60;

        if (currentTime - generateTime > tenMinutesInSeconds) {
            log.warn("Flux result image has expired, generateTime: {}, currentTime: {}",
                    generateTime, currentTime);
            return null;
        }

        // 4. 图片未过期，下载并上传到MinIO
        try {
            byte[] bytes = downloadVideoOrImage(fluxResult);
            minioUtil.uploadToMinio(bytes, userBucket, objectName, "image/png");
            return userBucket + "/" + objectName;
        } catch (Exception e) {
            log.error("Failed to download or upload Flux result image", e);
            return null;
        }
    }

    private GenerateResultVO getFluxResultAndSave(String taskId) {
        Generate generate = selectByUniqueId(taskId);
        if (Objects.nonNull(generate)) {
            GenerateDetail generateDetail = generateDetailMapper.selectOne(new QueryWrapper<GenerateDetail>().eq("generate_id", generate.getId()));
            Long accountId = generate.getAccountId();
            String objectName = accountId + "/imageToSketch/" + taskId + ".png";
            String fluxResult = getFluxResult(taskId, objectName);
            if (Objects.isNull(generateDetail)) {
                if (StringUtil.isNullOrEmpty(fluxResult)) {
                    // 将积分暂扣区的积分移除
                    creditsService.deleteCreditsDeduction(accountId, taskId);
                    return new GenerateResultVO(taskId, "Fail");
                } else if (fluxResult.equals("Fail") || fluxResult.equals("Pending")) {
                    String status = fluxResult.equals("Fail") ? "Fail" : "Executing";
                    if (status.equals("Fail")) {
                        creditsService.deleteCreditsDeduction(accountId, taskId);
                    }
                    return new GenerateResultVO(taskId, status);
                }
                generateDetail = new GenerateDetail(generate.getId(), fluxResult,
                        MD5Utils.encryptFile(
                                minioUtil.getPreSignedUrl(fluxResult, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false),
                        LocalDateTime.now());
                generateDetailMapper.insert(generateDetail);
                // 扣积分
                Boolean flag = creditsService.taskCreditsDeduction(accountId, taskId);
                if (flag) creditsService.updateChangedCredits(String.valueOf(accountId), taskId);
            } else if (StringUtil.isNullOrEmpty(generateDetail.getUrl())) {
                // 结果已经存入db,一般走不到这条线
                generateDetail.setGenerateId(generate.getId());
                generateDetail.setUrl(fluxResult);
                generateDetail.setMd5(MD5Utils.encryptFile(
                        minioUtil.getPreSignedUrl(fluxResult, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), false));
                generateDetail.setUpdateDate(new Date());
                generateDetailMapper.updateById(generateDetail);
            }
            String url = generateDetail.getUrl();
            String category;
            if (generate.getLevel1Type().equals(SKETCH_BOARD.getRealName())) {
                category = pythonService.getClothCategory(url, extractGender(generate.getGenerateType()));
            } else {
                category = generate.getLevel2Type();
            }

            return new GenerateResultVO(taskId, generateDetail.getId(),
                    minioUtil.getPreSignedUrl(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME), "Success", category);
        } else {
            throw new BusinessException("unknown generate");
        }
    }

    private String addWhiteBackground(String minioPath) {
        // 1、先通过后缀判断输入图片类型有没有透明通道
        String extension = minioPath.substring(minioPath.lastIndexOf(".") + 1);

        // 2、如果有，为其添加白色背景
        if (extension.equals("png")) {
            return minioUtil.changeToWhiteBackground(minioPath);
        } else {
            log.info("图片 {} 没有透明通道, 不用添加白底", minioPath);
            return null;
        }
    }

    public com.alibaba.fastjson.JSONObject createParamsForMotion(PoseTransformDTO poseTransformDTO, String taskId) {

        com.alibaba.fastjson.JSONObject params = new com.alibaba.fastjson.JSONObject();
        params.put("tasks_id", taskId);
        // 判断当前使用的哪种生成模式
        MotionModeEnum motionModeEnum = MotionModeEnum.of(poseTransformDTO.getMode());
        switch (motionModeEnum) {
            case POSE_TO_VIDEO:
                params.put("pose_id", poseTransformDTO.getPoseId());
                params.put("image_url", poseTransformDTO.getProductImage());
                break;
            case PROMPT_TO_VIDEO:
                params.put("image_url", poseTransformDTO.getProductImage());
                params.put("prompt", poseTransformDTO.getPrompt());
                break;
            case FIRST_LAST_FRAME_TO_VIDEO:
                params.put("start_image_url", poseTransformDTO.getProductImage());
                params.put("end_image_url", poseTransformDTO.getLastFrameProductImage());
                params.put("prompt", poseTransformDTO.getPrompt());
                break;
            default:
                throw new BusinessException("unknown.mode");
        }
        params.put("api", motionModeEnum.getUrl());
        return params;
    }

    /**
     * 根据豆包错误码和详细信息返回用户友好的错误信息
     *
     * @param errorCode     错误码
     * @param detailMessage 详细错误信息
     * @return 用户友好的错误信息
     */
    private String getDoubaoErrorMessage(String errorCode, String detailMessage, String userLanguage) {
        boolean isEnglish = "ENGLISH".equals(userLanguage);

        if (errorCode == null) {
            return isEnglish ? "Image generation failed, please try again later" : "图像生成失败，请稍后重试";
        }

        switch (errorCode) {
            // === 用户输入问题（用户可以自行解决） ===

            // 敏感内容检测 - 用户需要修改提示词
            case "SensitiveContentDetected":
            case "SensitiveContentDetected.SevereViolation":
            case "SensitiveContentDetected.Violence":
            case "InputTextSensitiveContentDetected":
                return isEnglish ? "Your prompt contains sensitive content. Please modify your description and try again."
                        : "您的提示词包含敏感内容，请修改描述后重试";
            case "InputImageSensitiveContentDetected":
                return isEnglish ? "Your uploaded image contains sensitive content. Please use a different image and try again."
                        : "您上传的图片包含敏感内容，请更换图片后重试";
            case "InputVideoSensitiveContentDetected":
                return isEnglish ? "Your uploaded video contains sensitive content. Please use a different video and try again."
                        : "您上传的视频包含敏感内容，请更换视频后重试";
            case "OutputTextSensitiveContentDetected":
            case "OutputImageSensitiveContentDetected":
            case "OutputVideoSensitiveContentDetected":
                return isEnglish ? "The generated content may contain sensitive information. Please modify your prompt and try again."
                        : "生成的内容可能包含敏感信息，请修改提示词后重试";

            // 图片格式问题 - 用户需要检查图片
            case "InvalidImageURL.EmptyURL":
                return isEnglish ? "No image was uploaded. Please upload an image and try again."
                        : "未上传图片，请上传图片后重试";
            case "InvalidImageURL.InvalidFormat":
                return isEnglish ? "The image format is not supported or the image is corrupted. Please upload a valid image (JPG, PNG, etc.) and try again."
                        : "图片格式不支持或图片已损坏，请上传有效的图片格式（JPG、PNG等）后重试";

            // 内容长度问题 - 用户需要缩短内容
            case "OutofContextError":
                return isEnglish ? "Your prompt and image content is too long. Please shorten your description or use a smaller image and try again."
                        : "您的提示词和图片内容过长，请缩短描述或使用更小的图片后重试";

            // 请求频率问题 - 用户需要等待
            case "TooManyRequests":
                return isEnglish ? "You are sending requests too frequently. Please wait a moment and try again."
                        : "您的请求过于频繁，请稍等片刻后重试";

            // === 系统问题（需要联系管理员） ===

            // 参数格式错误 - 系统问题
            case "MissingParameter":
            case "InvalidParameter":
            case "Duplicate.Tags.Key":
            case "InvalidArgumentError":
            case "InvalidArgumentError.UnknownRole":
            case "InvalidArgumentError.InvalidImageDetail":
            case "InvalidArgumentError.InvalidPixelLimit":
                return isEnglish ? "System error: Invalid request parameters. Please contact the administrator."
                        : "系统错误：请求参数格式有误，请联系管理员";

            // 认证问题 - 系统问题
            case "AuthenticationError":
                return isEnglish ? "System error: Authentication failed. Please contact the administrator."
                        : "系统错误：身份验证失败，请联系管理员";

            // 服务配置问题 - 系统问题
            case "InvalidEndpoint.ClosedEndpoint":
            case "OperationDenied.InvalidState":
            case "OperationDenied.ConflictedValidationSet":
            case "NotFound.Model":
            case "NotFound.Endpoint":
            case "NotFound.Context":
            case "InvalidOperation.Conflict":
            case "ModelNotOpen":
                return isEnglish ? "System error: Service configuration issue. Please contact the administrator."
                        : "系统错误：服务配置问题，请联系管理员";

            // 配额/费用问题 - 系统问题
            case "QuotaExceeded.DailyQuota":
            case "QuotaExceeded.MonthlyQuota":
            case "QuotaExceeded.TotalQuota":
            case "QuotaExceeded.RPM":
            case "QuotaExceeded.TPM":
            case "QuotaExceeded.RPD":
            case "QuotaExceeded.TPD":
                return isEnglish ? "System error: Service quota exceeded. Please contact the administrator."
                        : "系统错误：服务配额已用完，请联系管理员";

            // 服务器错误 - 系统问题
            case "InternalError":
            case "ServiceUnavailable":
                return isEnglish ? "System error: Service is temporarily unavailable. Please contact the administrator or try again later."
                        : "系统错误：服务暂时不可用，请联系管理员或稍后重试";

            default:
                // 根据详细信息判断是用户问题还是系统问题
                if (detailMessage != null) {
                    if (detailMessage.contains("sensitive") || detailMessage.contains("敏感")) {
                        return isEnglish ? "Your content contains sensitive information. Please modify and try again."
                                : "您的内容包含敏感信息，请修改后重试";
                    } else if (detailMessage.contains("quota") || detailMessage.contains("配额") ||
                            detailMessage.contains("parameter") || detailMessage.contains("参数") ||
                            detailMessage.contains("auth") || detailMessage.contains("认证")) {
                        return isEnglish ? "System error occurred. Please contact the administrator."
                                : "系统错误，请联系管理员";
                    }
                }
                return isEnglish ? "Image generation failed. Please try again later or contact the administrator if the problem persists."
                        : "图像生成失败，请稍后重试，如问题持续请联系管理员";
        }
    }
}
