package com.tashow.cloud.ai.service.aisample;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleFileRespVO;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSamplePageReqVO;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleRelateTagVO;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleRespVO;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleSaveReqVO;
import com.tashow.cloud.ai.controller.admin.aisample.vo.FileUploadReqVO;
import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleDO;
import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagDO;
import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagGroupDO;
import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagRelateDO;
import com.tashow.cloud.ai.dal.dataobject.model.AiModelSampleRelateDO;
import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleMapper;
import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleTagGroupMapper;
import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleTagMapper;
import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleTagRelateMapper;
import com.tashow.cloud.ai.dal.mysql.model.AiModelMapper;
import com.tashow.cloud.ai.dal.mysql.model.AiModelSampleRelateMapper;
import com.tashow.cloud.common.pojo.PageResult;
import com.tashow.cloud.common.util.object.BeanUtils;
import com.tashow.cloud.fileapi.api.file.FileApi;
import com.tashow.cloud.mybatis.mybatis.core.util.MyBatisUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Service
@Validated
public class AiSampleServiceImpl implements AiSampleService {

    @Resource
    private AiSampleMapper aiSampleMapper;
    @Resource
    private AiSampleTagMapper aiSampleTagMapper;
    @Resource
    private AiSampleTagRelateMapper aiSampleTagRelateMapper;
    @Resource
    private AiSampleTagGroupMapper aiSampleTagGroupMapper;
    @Resource
    private AiModelSampleRelateMapper modelSampleRelateMapper;
    @Resource
    private AiModelMapper modelMapper;
    @Resource
    private FileApi fileApi;

    @Value("${file-server}")
    private String fileServer;

    @Override
    @SneakyThrows
    public List<AiSampleFileRespVO> createAiSample(FileUploadReqVO uploadReqVO) {
        List<AiSampleFileRespVO> result = new ArrayList<>();
        for (MultipartFile file : uploadReqVO.getFiles()) {
            String filename = file.getOriginalFilename();
            String filePath = fileApi.createFile(filename, "", file.getBytes());

            AiSampleDO sample = new AiSampleDO();
            sample.setSampleFilePath(filePath);
            sample.setSampleName(filename.substring(0, filename.lastIndexOf('.')));
            sample.setSuffix(filename.substring(filename.lastIndexOf('.') + 1));
            sample.setSampleMineType(file.getContentType());
            sample.setSampleSize(file.getSize());
            aiSampleMapper.insert(sample);

            result.add(new AiSampleFileRespVO().setFileUrl(filePath).setFileName(filename));
        }
        return result;
    }

    @Override
    public void updateAiSample(AiSampleSaveReqVO updateReqVO) {
        aiSampleMapper.updateById(BeanUtils.toBean(updateReqVO, AiSampleDO.class));
    }

    @Override
    public void updateAiSamples(List<AiSampleSaveReqVO> updateReqVO) {
        aiSampleMapper.updateBatch(BeanUtils.toBean(updateReqVO, AiSampleDO.class));
    }

    @Override
    public void relate(AiSampleRelateTagVO relateTagVO) {
        List<AiSampleTagRelateDO> relates = new ArrayList<>();
        for (Long sampleId : relateTagVO.getSampleIds()) {
            for (Long tagId : relateTagVO.getTagId()) {
                validateTagGroupRule(sampleId, tagId);
                AiSampleTagRelateDO exist = aiSampleTagRelateMapper.selectOne(new LambdaQueryWrapper<AiSampleTagRelateDO>()
                        .eq(AiSampleTagRelateDO::getSampleId, sampleId)
                        .eq(AiSampleTagRelateDO::getSampleTagId, tagId));
                if (exist == null) {
                    relates.add(new AiSampleTagRelateDO().setSampleId(sampleId).setSampleTagId(tagId));
                }
            }
        }
        if (CollUtil.isNotEmpty(relates)) {
            aiSampleTagRelateMapper.insertBatch(relates);
        }
    }

    @Override
    public void deleteRelate(AiSampleRelateTagVO relateTagVO) {
        List<Long> ids = new ArrayList<>();
        for (Long sampleId : relateTagVO.getSampleIds()) {
            for (Long tagId : relateTagVO.getTagId()) {
                AiSampleTagRelateDO exist = aiSampleTagRelateMapper.selectOne(new LambdaQueryWrapper<AiSampleTagRelateDO>()
                        .eq(AiSampleTagRelateDO::getSampleId, sampleId)
                        .eq(AiSampleTagRelateDO::getSampleTagId, tagId));
                if (exist != null) {
                    ids.add(exist.getId());
                }
            }
        }
        if (CollUtil.isNotEmpty(ids)) {
            aiSampleTagRelateMapper.deleteBatchIds(ids);
        }
    }

    @Override
    public void deleteAiSample(String ids) {
        aiSampleMapper.deleteByIds(Arrays.asList(ids.split(StrUtil.COMMA)));
    }

    @Override
    public AiSampleDO getAiSample(Long id) {
        AiSampleDO sample = aiSampleMapper.selectById(id);
        if (sample == null) {
            return null;
        }

        List<Long> tagIds = aiSampleTagRelateMapper.selectList(new LambdaQueryWrapper<AiSampleTagRelateDO>()
                        .eq(AiSampleTagRelateDO::getSampleId, id))
                .stream().map(AiSampleTagRelateDO::getSampleTagId).toList();
        sample.setTags(CollUtil.isEmpty(tagIds)
                ? List.of()
                : aiSampleTagMapper.selectList(new LambdaQueryWrapper<AiSampleTagDO>().in(AiSampleTagDO::getId, tagIds)));
        return sample;
    }

    @Override
    public PageResult<AiSampleDO> getAiSamplePage(AiSamplePageReqVO pageReqVO) {
        IPage<AiSampleDO> page = aiSampleMapper.getAiSamplePage(MyBatisUtils.buildPage(pageReqVO), pageReqVO);
        List<AiSampleDO> samples = page.getRecords();
        List<Long> sampleIds = samples.stream().map(AiSampleDO::getId).toList();
        if (CollUtil.isEmpty(sampleIds)) {
            return new PageResult<>(samples, page.getTotal());
        }

        List<AiSampleTagRelateDO> relates = aiSampleTagRelateMapper.selectList(new LambdaQueryWrapper<AiSampleTagRelateDO>()
                .in(AiSampleTagRelateDO::getSampleId, sampleIds));
        List<Long> tagIds = relates.stream().map(AiSampleTagRelateDO::getSampleTagId).distinct().toList();
        List<AiSampleTagDO> tags = CollUtil.isEmpty(tagIds)
                ? List.of()
                : aiSampleTagMapper.selectList(new LambdaQueryWrapper<AiSampleTagDO>().in(AiSampleTagDO::getId, tagIds));

        Map<Long, AiSampleTagDO> tagMap = new HashMap<>();
        for (AiSampleTagDO tag : tags) {
            tagMap.put(tag.getId(), tag);
        }
        Map<Long, List<AiSampleTagDO>> sampleTagMap = new HashMap<>();
        for (AiSampleTagRelateDO relate : relates) {
            AiSampleTagDO tag = tagMap.get(relate.getSampleTagId());
            if (tag != null) {
                sampleTagMap.computeIfAbsent(relate.getSampleId(), k -> new ArrayList<>()).add(tag);
            }
        }
        for (AiSampleDO sample : samples) {
            sample.setTags(sampleTagMap.getOrDefault(sample.getId(), List.of()));
        }
        return new PageResult<>(samples, page.getTotal());
    }

    @Override
    public void downloadSamples(String ids, HttpServletResponse response) throws IOException {
        List<AiSampleDO> samples = aiSampleMapper.selectBatchIds(Arrays.asList(ids.split(StrUtil.COMMA)));
        if (samples.size() == 1) {
            AiSampleDO sample = samples.get(0);
            byte[] bytes = HttpUtil.downloadBytes(fileServer + sample.getSampleFilePath());
            response.setContentType(sample.getSampleMineType());
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(sample.getSampleName(), "UTF-8"));
            response.getOutputStream().write(bytes);
            return;
        }

        response.setContentType("application/zip");
        response.setHeader("Content-Disposition", "attachment; filename=samples.zip");
        try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
            for (AiSampleDO sample : samples) {
                byte[] bytes = HttpUtil.downloadBytes(fileServer + sample.getSampleFilePath());
                zos.putNextEntry(new ZipEntry(sample.getSampleName()));
                zos.write(bytes);
                zos.closeEntry();
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void relateSamples(List<Long> sampleIds, List<Long> modelIds) {
        if (CollUtil.isEmpty(sampleIds)) {
            return;
        }
        boolean noModels = CollUtil.isEmpty(modelIds);
        for (Long sampleId : sampleIds) {
            modelSampleRelateMapper.delete(new LambdaQueryWrapper<AiModelSampleRelateDO>()
                    .eq(AiModelSampleRelateDO::getSampleId, sampleId));
            if (noModels) {
                continue;
            }
            for (Long modelId : modelIds) {
                modelSampleRelateMapper.insert(new AiModelSampleRelateDO().setSampleId(sampleId).setModelId(modelId));
            }
        }
    }

    private void validateTagGroupRule(Long sampleId, Long tagId) {
        AiSampleTagDO currentTag = aiSampleTagMapper.selectById(tagId);
        if (currentTag == null || !Integer.valueOf(1).equals(currentTag.getTagType())) {
            return;
        }

        Long groupId = getFirstGroupId(currentTag.getGroupId());
        if (groupId == null) {
            return;
        }
        AiSampleTagGroupDO group = aiSampleTagGroupMapper.selectById(groupId);
        if (group != null && Integer.valueOf(2).equals(group.getGroupType())) {
            return;
        }

        List<AiSampleTagRelateDO> existingRelates = aiSampleTagRelateMapper.selectList(new LambdaQueryWrapper<AiSampleTagRelateDO>()
                .eq(AiSampleTagRelateDO::getSampleId, sampleId));
        for (AiSampleTagRelateDO relate : existingRelates) {
            if (tagId.equals(relate.getSampleTagId())) {
                continue;
            }
            AiSampleTagDO existingTag = aiSampleTagMapper.selectById(relate.getSampleTagId());
            if (existingTag != null && parseGroupIds(existingTag.getGroupId()).contains(groupId)) {
                aiSampleTagRelateMapper.deleteById(relate.getId());
            }
        }
    }

    @Override
    public AiSampleRespVO buildSampleRespVO(AiSampleDO sample) {
        AiSampleRespVO respVO = BeanUtils.toBean(sample, AiSampleRespVO.class);
        respVO.setEnumTags(buildEnumTags(sample.getId()));
        respVO.setRelatedModels(buildRelatedModels(sample.getId()));
        return respVO;
    }

    private List<AiSampleRespVO.TagInfo> buildEnumTags(Long sampleId) {
        List<Long> tagIds = aiSampleTagRelateMapper.selectList(new LambdaQueryWrapper<AiSampleTagRelateDO>()
                        .eq(AiSampleTagRelateDO::getSampleId, sampleId))
                .stream().map(AiSampleTagRelateDO::getSampleTagId).toList();
        if (CollUtil.isEmpty(tagIds)) {
            return List.of();
        }

        List<AiSampleTagDO> tags = aiSampleTagMapper.selectBatchIds(tagIds).stream()
                .filter(tag -> Integer.valueOf(1).equals(tag.getTagType())).toList();
        Map<Long, String> groupNames = new HashMap<>();
        Set<Long> groupIds = new HashSet<>();
        for (AiSampleTagDO tag : tags) {
            Long groupId = getFirstGroupId(tag.getGroupId());
            if (groupId != null) {
                groupIds.add(groupId);
            }
        }
        for (AiSampleTagGroupDO group : aiSampleTagGroupMapper.selectBatchIds(groupIds)) {
            groupNames.put(group.getId(), group.getGroupName());
        }
        return tags.stream().map(tag -> {
            Long groupId = getFirstGroupId(tag.getGroupId());
            return AiSampleRespVO.TagInfo.builder()
                    .id(tag.getId())
                    .tagName(tag.getTagName())
                    .enumValue(tag.getEnumValue())
                    .groupId(groupId)
                    .groupName(groupId == null ? null : groupNames.get(groupId))
                    .build();
        }).toList();
    }

    private List<AiSampleRespVO.ModelInfo> buildRelatedModels(Long sampleId) {
        List<Long> modelIds = modelSampleRelateMapper.selectList(new LambdaQueryWrapper<AiModelSampleRelateDO>()
                        .eq(AiModelSampleRelateDO::getSampleId, sampleId))
                .stream().map(AiModelSampleRelateDO::getModelId).toList();
        if (CollUtil.isEmpty(modelIds)) {
            return List.of();
        }
        return modelMapper.selectBatchIds(modelIds).stream()
                .map(model -> AiSampleRespVO.ModelInfo.builder()
                        .id(model.getId())
                        .modelName(model.getModelName())
                        .version(model.getVersion())
                        .build())
                .toList();
    }

    private Long getFirstGroupId(String groupIds) {
        return Long.parseLong(groupIds.split(StrUtil.COMMA)[0].trim());
    }

    private List<Long> parseGroupIds(String groupIds) {
        return Arrays.stream(groupIds.split(StrUtil.COMMA))
                .map(String::trim)
                .map(Long::parseLong)
                .toList();
    }
}

