package com.ai.da.service.impl;

import com.ai.da.common.config.exception.BusinessException;
import com.ai.da.common.context.UserContext;
import com.ai.da.common.enums.GenerateModeEnum;
import com.ai.da.common.enums.ModelNameEnum;
import com.ai.da.common.utils.*;
import com.ai.da.mapper.primary.CollectionElementMapper;
import com.ai.da.mapper.primary.GenerateCancelMapper;
import com.ai.da.mapper.primary.GenerateDetailMapper;
import com.ai.da.mapper.primary.GenerateMapper;
import com.ai.da.mapper.primary.entity.*;
import com.ai.da.model.dto.GenerateLikeDTO;
import com.ai.da.model.dto.GenerateThroughImageTextDTO;
import com.ai.da.model.dto.GenerateToPythonDTO;
import com.ai.da.model.vo.*;
import com.ai.da.python.PythonService;
import com.ai.da.service.CollectionElementService;
import com.ai.da.service.GenerateService;
import com.ai.da.service.LibraryService;
import com.ai.da.service.RabbitMQService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.minio.errors.MinioException;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;

import static com.ai.da.common.enums.CollectionLevel1TypeEnum.*;

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

    @Resource
    private CollectionElementMapper collectionElementMapper;

    @Resource
    private GenerateDetailMapper generateDetailMapper;

    @Resource
    private LibraryService libraryService;

    @Resource
    private PythonService pythonService;

    @Resource
    private CollectionElementService collectionElementService;

    @Resource
    private MinioUtil minioUtil;

    @Resource
    private RabbitMQService rabbitMQService;

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private GenerateCancelMapper generateCancelMapper;

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

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

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

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

    @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 GenerateCollectionVO generateThroughImageText(GenerateThroughImageTextDTO generateThroughImageTextDTO) {
        // 1、获取用户信息
        Long accountId = generateThroughImageTextDTO.getUserId();
        String generateType = generateThroughImageTextDTO.getGenerateType();

        // 2、判断必须入参是否为非空
        Generate generate = new Generate();
        generate.setAccountId(accountId);
        generate.setUniqueId(generateThroughImageTextDTO.getUniqueId());
        generate.setLevel1Type(generateThroughImageTextDTO.getLevel1Type());
        // 当level1type是sketchboard时，存数据库需要加上当前性别
        generate.setGenerateType(generate.getLevel1Type().equals(SKETCH_BOARD.getRealName()) ?
                generateType + " (" + generateThroughImageTextDTO.getGender() + ")" :
                generateType);
        generate.setModelName(StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getVersion()) ? ModelNameEnum.MODEL_0.getCode() : generateThroughImageTextDTO.getVersion());
        generate.setCreateDate(DateUtil.getByTimeZone(generateThroughImageTextDTO.getTimeZone()));
        generate.setElementSource(StringUtil.isNullOrEmpty(generateThroughImageTextDTO.getDesignType()) ? null : generateThroughImageTextDTO.getDesignType());

        String text = generateThroughImageTextDTO.getText();
        Long elementId = generateThroughImageTextDTO.getCollectionElementId();
        validateGeneraType(generate, text, elementId, generateType);

        // 2.1 sketch或print在t_collection_element表/t_library表中的信息是否需要更新 如 level2Type
        CollectionElement collectionElement = collectionElementService.editLevel2Type(elementId, generateThroughImageTextDTO.getLevel2Type(), generateThroughImageTextDTO.getDesignType());

        // 3、向模型发起请求
        int mode = GenerateModeEnum.TEXT.getValue().equals(generateType) ?
                GenerateModeEnum.TEXT.getCode() :
                GenerateModeEnum.TEXT_IMAGE.getCode();
        String category = generateThroughImageTextDTO.getLevel1Type().equals(SKETCH_BOARD.getRealName()) ? "sketch" :
                generateThroughImageTextDTO.getLevel1Type().equals(PRINT_BOARD.getRealName()) ? "print" : "moodboard";
        AsyncCallerUtil asyncCallerUtil = new AsyncCallerUtil();
        List<String> generatedSketchUrl = asyncCallerUtil.generate(new GenerateToPythonDTO(accountId, Objects.isNull(collectionElement) ? "" : collectionElement.getUrl(),
                category, text, mode, "1", generateThroughImageTextDTO.getGender(), generateThroughImageTextDTO.getUniqueId()));
//        List<String> generatedSketchUrl = pythonService.generateSketchOrPrint(new GenerateToPythonDTO(accountId, Objects.isNull(elementId) ? null : collectionElement.getUrl(),
//                category, text, mode, "1", generateThroughImageTextDTO.getGender()));
        log.info("generate 响应 ： " + generatedSketchUrl);
        if (CollectionUtils.isEmpty(generatedSketchUrl)) {
            return null;
        }

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

        // 5、处理模型返回的数据
        // 5.1 将相应的url保存到数据库
        List<GenerateCollectionItemVO> generatedCollectionItems = new ArrayList<>();
        generatedSketchUrl.forEach(item -> {
            GenerateDetail generateDetail = new GenerateDetail();
            GenerateCollectionItemVO generateCollectionItemVO = new GenerateCollectionItemVO();
            String md5 = MD5Utils.encryptFile(minioUtil.getPresignedUrl(item, 24 * 60), Boolean.FALSE);
            // 通过MD5值和level1Type,判断不同level1Type下相同的图片是否被like过
            List<Map<String, Long>> libraryIdList = generateDetailMapper.getLibraryIdThroughMD5(md5, generateThroughImageTextDTO.getLevel1Type());
            if (!libraryIdList.isEmpty()) {
                generateDetail.setIsLike((byte) 1);
                generateDetail.setLibraryId(libraryIdList.get(0).get("library_id"));
                generateCollectionItemVO.setIsLiked(Boolean.TRUE);
            }
            generateDetail.setUrl(item);
            generateDetail.setGenerateId(generate.getId());
            generateDetail.setCreateDate(DateUtil.getByTimeZone(generateThroughImageTextDTO.getTimeZone()));
            generateDetail.setMd5(md5);
            generateDetailMapper.insert(generateDetail);

            generateCollectionItemVO.setGenerateItemId(generateDetail.getId());
            generateCollectionItemVO.setGenerateItemUrl(minioUtil.getPresignedUrl(item, 24 * 60));
            generatedCollectionItems.add(generateCollectionItemVO);
        });

        // 6、将模型返回的图片地址返回给前端
        Long collectionId = Objects.isNull(collectionElement) ? null : collectionElement.getCollectionId();
        return new GenerateCollectionVO(generate.getId(), collectionId, generatedCollectionItems);
    }

    private void validateGeneraType(Generate generate, String text, Long elementId, String generateType) {
        switch (generateType) {
            case "text":
                if (StringUtil.isNullOrEmpty(text)) {
                    throw new BusinessException("please.input.the.caption");
                }
                generate.setText(text);
                break;
            case "image":
                if (Objects.isNull(elementId)) {
                    throw new BusinessException("please.choose.an.image");
                }
                generate.setElementId(elementId);
                break;
            case "text-image":
                if (StringUtil.isNullOrEmpty(text) || Objects.isNull(elementId)) {
                    throw new BusinessException("please.input.the.caption.and.choose.an.image");
                }
                generate.setText(text);
                generate.setElementId(elementId);
            default:
        }
    }

    @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");
            }
        }
        // 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());
        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) {
        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.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);
    }

    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);
    }

    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 generateThroughImageTextDTO) {
        // 1、参数检查，判断必须参数是否为空
        if (Objects.isNull(generateThroughImageTextDTO.getUserId())) {
            throw new BusinessException("userId cannot be empty");
        }
        String generateType = generateThroughImageTextDTO.getGenerateType();
        if (!GenerateModeEnum.getGenerateModeList().contains(generateType)) {
            throw new BusinessException("unknown.generate.type");
        }

        // 判断试用用户是否还有剩余试用机会
        int trialsCount = 0;
        if (generateThroughImageTextDTO.getIsTestUser()){
            trialsCount = getTrialsCount(generateThroughImageTextDTO.getUserId(), generateThroughImageTextDTO.getLevel1Type());
            if (trialsCount >= 2){
                return new PrepareForGenerateVO(0);
            }
        }

        String text = generateThroughImageTextDTO.getText();
        Long elementId = generateThroughImageTextDTO.getCollectionElementId();
        validateGeneraType(new Generate(), text, elementId, generateType);

        // 2、生成唯一id 使用uuid
        String uuid = UUID.randomUUID().toString();

//        SnowflakeUtil idWorker = new SnowflakeUtil(0, 0);
//        long snowflakeId = idWorker.nextId();

        int num = 1;
        // 判断已经正常生成结果的uuid或正在排队的uuid中是否有相同的id
        while ((redisUtil.isElementExistsInMap(resultMapKey, uuid) ||
                redisUtil.isElementExistsInZSet(consumptionOrderKey, uuid))
                && num < 10) {
            uuid = UUID.randomUUID().toString();
            num++;
        }
        // 无依据确定的数字
        if (num > 10) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            uuid = UUID.randomUUID().toString();
        }
        generateThroughImageTextDTO.setUniqueId(uuid);
        String jsonString = JSON.toJSONString(generateThroughImageTextDTO);

        // 3、加入redis排队，便于获取实时排队信息
        Double maxScore = redisUtil.getMaxScore(consumptionOrderKey);
        redisUtil.addToZSet(consumptionOrderKey, uuid, maxScore);

        // 4、将消息发布到MQ消息队列
        rabbitMQService.publishMessageToGenerate(jsonString);

        // 5、返回唯一id
        return new PrepareForGenerateVO(uuid, 2 - trialsCount);
    }

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

    @Override
    public GenerateCollectionVO getGenerateResult(String uniqueId) {
        // 1、判断该请求是否已经异常
        Boolean isMember = redisUtil.isElementExistsInMap(exceptionMapKey, uniqueId);
        if (isMember) {
            throw new BusinessException("generate.interface.error");
        }

        // 2、判断该请求是否还在排队
        Boolean existsInZSet = redisUtil.isElementExistsInZSet(consumptionOrderKey, uniqueId);
        if (existsInZSet) {
            // 排队中，给出当前排序位置,rank从0开始
            Long rankPosition = getRankPosition(uniqueId);
            // 有9个消费者，所以当rank>8即当前请求至少排在第九位时，其实际排队位置为9-8+1，当rank <=8，请求均在处理中
            return new GenerateCollectionVO(rankPosition > 8L ? rankPosition - 8 + 1 : 1L);
        }

        // 3、判断redis中有没有
        boolean hasHashKey = redisUtil.isElementExistsInMap(resultMapKey, uniqueId);
        if (hasHashKey) {
            // 3.1 有直接从redis中拿
            String resultString = redisUtil.getMapValue(resultMapKey, uniqueId);
            return JSONObject.parseObject(resultString, GenerateCollectionVO.class);
        }

        // 3.2 判断数据库中有没有
        Generate generate = selectByUniqueId(uniqueId);
        if (Objects.isNull(generate)) {
            // 3.3 还没执行完，给出当前位置
            return new GenerateCollectionVO(1L);
        }
        Long generateId = generate.getId();
        QueryWrapper<GenerateDetail> qw = new QueryWrapper<>();
        qw.eq("generate_id", generateId);
        List<GenerateDetail> generateDetails = generateDetailMapper.selectList(qw);
        if (CollectionUtils.isEmpty(generateDetails)) {
            // 会有这种情况吗？存到generate中，但是还没存到generateDetail中
            return new GenerateCollectionVO(1L);
        }

        List<GenerateCollectionItemVO> generatedCollectionItems = new ArrayList<>();
        generateDetails.forEach(item -> {
            GenerateCollectionItemVO generateCollectionItemVO = new GenerateCollectionItemVO();
            generateCollectionItemVO.setGenerateItemId(item.getId());
            generateCollectionItemVO.setGenerateItemUrl(minioUtil.getPresignedUrl(item.getUrl(), 24 * 60));
            generatedCollectionItems.add(generateCollectionItemVO);
        });

        return new GenerateCollectionVO(generateId, null, generatedCollectionItems);
    }

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

        return getOne(qw);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelGenerate(Long userId, String uniqueId, String timeZone) {
        // 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);
            }
        }

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

    // 判断试用用户试用generate机会是否使用完毕
    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) {
            trialsCount = 1;
        } else if (getGenerateList.size() == 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);
    }
}
