package cn.iocoder.yudao.module.llm.service.application;

import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.llm.controller.admin.application.vo.ApplicationPageReqVO;
import cn.iocoder.yudao.module.llm.controller.admin.application.vo.ApplicationRespVO;
import cn.iocoder.yudao.module.llm.controller.admin.application.vo.ApplicationSaveReqVO;
import cn.iocoder.yudao.module.llm.controller.admin.prompttemplates.vo.PromptTemplatesRespVO;
import cn.iocoder.yudao.module.llm.dal.dataobject.application.ApplicationDO;
import cn.iocoder.yudao.module.llm.dal.dataobject.label.LabelDO;
import cn.iocoder.yudao.module.llm.dal.dataobject.prompttemplates.PromptTemplatesDO;
import cn.iocoder.yudao.module.llm.dal.mysql.application.ApplicationMapper;
import cn.iocoder.yudao.module.llm.dal.mysql.label.LabelMapper;
import cn.iocoder.yudao.module.llm.framework.backend.config.LLMBackendProperties;
import cn.iocoder.yudao.module.llm.service.prompttemplates.PromptTemplatesService;
import cn.iocoder.yudao.module.llm.service.prompttemplatesbackup.PromptTemplatesBackupService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.llm.enums.ErrorCodeConstants.APPLICATION_NAME_NOT_EXISTS;
import static cn.iocoder.yudao.module.llm.enums.ErrorCodeConstants.APPLICATION_NOT_EXISTS;

/**
 * 大模型应用 Service 实现类
 *
 * @author 华大大模型
 */
@Service
@Validated
@Slf4j
public class ApplicationServiceImpl implements ApplicationService {

    @Resource
    private ApplicationMapper applicationMapper;

    @Resource
    private AdminUserApi adminUserApi;

    @Resource
    private LabelMapper labelMapper;

    @Resource
    @Lazy
    private PromptTemplatesService promptTemplatesService;

    @Lazy
    @Resource
    private PromptTemplatesBackupService promptTemplatesBackupService;

    @Resource
    private LLMBackendProperties llmBackendProperties;

    @Override
    public Long createApplication (ApplicationSaveReqVO createReqVO) {

        String appName = createReqVO.getAppName();
        LambdaQueryWrapper<ApplicationDO> queryWrapper = new LambdaQueryWrapperX<>();
        queryWrapper.eq(ApplicationDO::getAppName, appName);
        List<ApplicationDO> applicationDOS = applicationMapper.selectList(queryWrapper);
        if (applicationDOS.size() > 0) {
            throw exception(APPLICATION_NAME_NOT_EXISTS);
        }
        // 插入
        ApplicationDO application = BeanUtils.toBean(createReqVO, ApplicationDO.class);
        String apikey = getApikey();
        application.setApiKey(apikey);
        String replace = llmBackendProperties.getApplicationApi().replace("apiKey", apikey);
        application.setApiUrl(replace);
        application.setPrompt(createReqVO.getSystemPrompt());
        applicationMapper.insert(application);

        //prompt使用量+1
        Long promptId = application.getPromptId();
        if (promptId != null) {
            PromptTemplatesRespVO promptTemplates = promptTemplatesService.getPromptTemplates(promptId);
            PromptTemplatesDO promptTemplatesDO = new PromptTemplatesDO();
            promptTemplatesDO.setUseCount(promptTemplates.getUseCount() == null ? 1 : promptTemplates.getUseCount() + 1);
            promptTemplatesDO.setId(promptTemplates.getId());
            promptTemplatesService.updatePromptTemplatesById(promptTemplatesDO);
        }
        // 返回
        return application.getId();
    }

    public String getApikey () {
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 30; i++) {
            // 生成一个随机的大小写字母
            char c = (char) (random.nextBoolean() ? // 随机选择大小写
                    'A' + random.nextInt(26) : // 生成大写字母
                    'a' + random.nextInt(26)); // 生成小写字母
            sb.append(c);
        }
        Long count = applicationMapper.selectCount(new LambdaQueryWrapper<ApplicationDO>()
                .eq(ApplicationDO::getApiKey, sb.toString()));
        if (count > 0) {
            getApikey();
        }
        return sb.toString();
    }

    @Override
    public void updateApplication (ApplicationSaveReqVO updateReqVO) {
        // 校验存在
        validateApplicationExists(updateReqVO.getId());
        // 更新
        ApplicationDO updateObj = BeanUtils.toBean(updateReqVO, ApplicationDO.class);
        updateObj.setPrompt(updateReqVO.getSystemPrompt());
        applicationMapper.updateById(updateObj);
    }

    @Override
    public void deleteApplication (Long id) {
        // 校验存在
        validateApplicationExists(id);

        // 校验应用使用情况
        validateApplicationUse(id);

        // 删除
        LambdaUpdateWrapper<ApplicationDO> wrapper = new LambdaUpdateWrapper<>();

        wrapper.eq(ApplicationDO::getId, id)
                .set(ApplicationDO::getDeleted, true);

        applicationMapper.update(null, wrapper);
    }

    /**
     * 校验应用使用情况
     *
     * @param id 应用id
     */
    private void validateApplicationUse (Long id) {
        // 获取应用信息，用来返回错误信息
        ApplicationDO applicationDO = applicationMapper.selectById(id);
        String appName = applicationDO.getAppName();

        // 检查应用是否在 Prompt模版 中有使用
        validateAppUsesInPromptTemplate(id, appName);

        // 检查应用是否在 Prompt模版备份 中有使用
        validateAppUsesInPromptTemplateBackup(id, appName);
    }

    /**
     * 检查应用是否在 Prompt 模版 有使用
     *
     * @param id   应用id
     * @param name 应用名称
     */
    private void validateAppUsesInPromptTemplate (Long id, String name) {
        Map<Long, String> template = promptTemplatesService.getPromptTemplateByAppId(id);

        if (CollectionUtils.isNotEmpty(template)) {
            String msg = String.format("应用【%s】在 Prompt模版 %s 有使用，请先删除Prompt模版中的应用", name, template.values());
            throw exception(new ErrorCode(10006_001, msg));
        }
    }

    /**
     * 检查应用是否在 Prompt模版备份 有使用
     *
     * @param id   应用id
     * @param name 应用名称
     */
    private void validateAppUsesInPromptTemplateBackup (Long id, String name) {
        Map<Long, String> template = promptTemplatesBackupService.getPromptTemplateBackByAppId(id);
        if (CollectionUtils.isNotEmpty(template)) {
            String msg = String.format("应用【%s】在 Prompt模版备份 %s 有使用，请先取消Prompt模版备份", name, template.values());
            throw exception(new ErrorCode(10006_002, msg));
        }
    }

    private void validateApplicationExists (Long id) {
        if (applicationMapper.selectById(id) == null) {
            throw exception(APPLICATION_NOT_EXISTS);
        }
    }

    @Override
    public ApplicationRespVO getApplication (Long id) {
        ApplicationDO applicationDO = applicationMapper.selectById(id);

        if (applicationDO == null) {
            throw exception(APPLICATION_NOT_EXISTS);
        }

        ApplicationRespVO bean = BeanUtils.toBean(applicationDO, ApplicationRespVO.class);
        if (bean.getAppLabel() != null && !bean.getAppLabel().equals("")) {
            String[] split = bean.getAppLabel().split(",");
            LambdaQueryWrapper<LabelDO> wrapper = new LambdaQueryWrapper<LabelDO>()
                    .in(LabelDO::getId, split);
            List<LabelDO> labelDOS = labelMapper.selectList(wrapper);
            List<String> collect = labelDOS.stream().map(LabelDO::getLabelName).collect(Collectors.toList());
            bean.setAppLabelName(String.join(",", collect));
            if (applicationDO.getAppModelType() == 0) {

            }
        }
        bean.setSystemPrompt(applicationDO.getPrompt());
        return bean;
    }

    @Override
    public PageResult<ApplicationRespVO> getApplicationPage (ApplicationPageReqVO pageReqVO) {
        PageResult<ApplicationDO> applicationDOPageResult = applicationMapper.selectPage(pageReqVO);
        PageResult<ApplicationRespVO> result = BeanUtils.toBean(applicationDOPageResult, ApplicationRespVO.class);
        List<Long> ids = applicationDOPageResult.getList().stream().map(applicationDO -> Long.parseLong(applicationDO.getCreator())).collect(Collectors.toList());
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMapApplication(ids);
        List<ApplicationRespVO> list = result.getList();
        if (CollectionUtils.isEmpty(list)) {
            return result;
        }
        for (ApplicationRespVO applicationRespVO : list) {
            String creator = applicationRespVO.getCreator();
            AdminUserRespDTO userRespDTO = userMap.get(Long.parseLong(creator));
            if (userRespDTO != null){
                applicationRespVO.setCreatorName(userRespDTO.getNickname());
            }
        }
        return result;
    }

    @Override
    public List<ApplicationRespVO> getApplicationList (ApplicationPageReqVO pageReqVO) {
        LambdaQueryWrapper<ApplicationDO> queryWrapper = new LambdaQueryWrapper<>();
        if (pageReqVO.getAppName() != null && !pageReqVO.getAppName().equals("")) {
            queryWrapper.like(ApplicationDO::getAppName, pageReqVO.getAppName());
        }
        if (pageReqVO.getAppCategory() != null && !pageReqVO.getAppCategory().equals("")) {
            queryWrapper.eq(ApplicationDO::getAppCategory, pageReqVO.getAppCategory());
        }
        if (pageReqVO.getCreator() != null && !pageReqVO.getCreator().equals("")) {
            queryWrapper.eq(ApplicationDO::getCreator, pageReqVO.getCreator());
        }
        if (pageReqVO.getExclude() != null && !pageReqVO.getExclude().equals("")) {
            queryWrapper.ne(ApplicationDO::getCreator, pageReqVO.getExclude());
        }
        queryWrapper.eq(ApplicationDO::getDeleted, false);
        queryWrapper.orderByDesc(ApplicationDO::getId);
        List<ApplicationDO> applicationDOPageResult = applicationMapper.selectList(queryWrapper);

        if (CollectionUtils.isEmpty(applicationDOPageResult)) {
            return new ArrayList<>();
        }
        List<ApplicationRespVO> result = BeanUtils.toBean(applicationDOPageResult, ApplicationRespVO.class);
        if (CollectionUtils.isEmpty(result)) {
            return new ArrayList<>();
        }
        List<Long> ids = applicationDOPageResult.stream().map(applicationDO -> Long.parseLong(applicationDO.getCreator())).collect(Collectors.toList());
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMapApplication(ids);
        for (ApplicationRespVO applicationRespVO : result) {
            String creator = applicationRespVO.getCreator();
            AdminUserRespDTO userRespDTO = userMap.get(Long.parseLong(creator));
            if (userRespDTO != null){
                applicationRespVO.setCreatorName(userRespDTO.getNickname());
            }
            if (applicationRespVO.getAppLabel() != null && !applicationRespVO.getAppLabel().equals("")) {
                String[] split = applicationRespVO.getAppLabel().split(",");
                LambdaQueryWrapper<LabelDO> wrapper = new LambdaQueryWrapper<LabelDO>()
                        .in(LabelDO::getId, split);
                List<LabelDO> labelDOS = labelMapper.selectList(wrapper);
                List<String> collect = labelDOS.stream().map(LabelDO::getLabelName).collect(Collectors.toList());
                applicationRespVO.setAppLabelName(String.join(",", collect));
            }
        }
        return result;
    }

    @Override
    public List<ApplicationDO> getList () {
        return applicationMapper.selectList();
    }

    @Override
    public ApplicationDO getByApiKey (String apiKey) {
        LambdaQueryWrapper<ApplicationDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ApplicationDO::getApiKey, apiKey);
        return applicationMapper.selectOne(queryWrapper);
    }

    /**
     * 获取应用Map
     *
     * @return Map<Long, String>
     */
    @Override
    public Map<Long, String> getApplicatioMap () {
        List<ApplicationDO> appList = this.getList();
        if (CollectionUtils.isEmpty(appList)) {
            return Collections.emptyMap();
        }

        return appList.stream()
                .collect(Collectors.toMap(ApplicationDO::getId, ApplicationDO::getAppName));
    }


    /**
     * 通用的根据条件查询 ApplicationDO 列表并转换为 Map 的方法
     *
     * @param condition   条件，使用 BiFunction 接收 LambdaQueryWrapper 和参数并设置条件
     * @param keyMapper   ApplicationDO 对象映射为 Map 的键
     * @param valueMapper ApplicationDO 对象映射为 Map 的值
     * @param params      查询参数数组
     * @return 应用的 Map 集合
     */
    private Map<Long, String> getApplicationMap (BiFunction<LambdaQueryWrapper<ApplicationDO>, Object[], LambdaQueryWrapper<ApplicationDO>> condition,
                                                 Function<ApplicationDO, Long> keyMapper,
                                                 Function<ApplicationDO, String> valueMapper,
                                                 Object[] params) {

        if (params == null || params.length == 0) {
            return Collections.emptyMap();
        }

        // 查询符合条件的应用
        LambdaQueryWrapper<ApplicationDO> queryWrapper = new LambdaQueryWrapper<>();
        // 设置条件
        queryWrapper = condition.apply(queryWrapper, params);
        log.info("condition: {}", queryWrapper.getSqlSegment());

        List<ApplicationDO> applications = applicationMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(applications)) {
            return Collections.emptyMap();
        }

        log.info("params: {}", Arrays.asList(params));
        return applications.stream().collect(Collectors.toMap(keyMapper, valueMapper));
    }


    /**
     * 根据标签 id 获取应用
     *
     * @param labelId 标签 id
     * @return 应用的 Map 集合
     */
    @Override
    public Map<Long, String> getApplicationByLabelId (Long labelId) {
        return getApplicationMap((wrapper, params) -> {
            Long id = (Long) params[0];
            return wrapper.select(ApplicationDO::getId, ApplicationDO::getAppName).eq(ApplicationDO::getAppLabel, id);
        }, ApplicationDO::getId, ApplicationDO::getAppName, new Object[]{labelId});

    }


    /**
     * 根据 Prompt 模板 id 获取应用
     *
     * @param templateId Prompt 模板 id
     * @return 应用的 Map 集合
     */
    @Override
    public Map<Long, String> validatePromptTemplateUsesInApp (Long templateId) {
        return getApplicationMap((wrapper, params) -> {
            Long id = (Long) params[0];
            return wrapper.select(ApplicationDO::getId, ApplicationDO::getAppName).eq(ApplicationDO::getPromptId, id);
        }, ApplicationDO::getId, ApplicationDO::getAppName, new Object[]{templateId});
    }


    /**
     * 根据知识库 id 获取应用
     *
     * @param knowledgeId 知识库 id
     * @return 应用的 Map 集合
     */
    @Override
    public Map<Long, String> getApplicationByKnowledgeId (Long knowledgeId) {
        return getApplicationMap((wrapper, params) -> {
            Long id = (Long) params[0];
            return wrapper.select(ApplicationDO::getId, ApplicationDO::getAppName).eq(ApplicationDO::getModelServiceId, id);
        }, ApplicationDO::getId, ApplicationDO::getAppName, new Object[]{knowledgeId});
    }

    /**
     * 根据 基础模型id 获取 应用
     *
     * @param baseModelId 基础模型id
     * @return 应用
     */
    @Override
    public Map<Long, String> getApplicationByBaseModelId (Long baseModelId) {
        return getApplicationByBaseModelIdAndType(baseModelId, 1);
    }

    /**
     * 根据 模型服务id 获取 应用
     *
     * @param modelServiceId 模型服务id
     * @return 应用
     */
    @Override
    public Map<Long, String> getApplicationByModelServiceId (Long modelServiceId) {
        return getApplicationByBaseModelIdAndType(modelServiceId, 0);
    }

    /**
     * 根据 模型id 和模型类型 获取 应用
     *
     * @param modelId      模型id
     * @param appModelType 应用模型类型 0:我的模型 1:预制模型
     * @return 应用
     */
    public Map<Long, String> getApplicationByBaseModelIdAndType (Long modelId, Integer appModelType) {
        return getApplicationMap((wrapper, params) -> {
            Long id = (Long) params[0];
            Integer type = (Integer) params[1];
            return wrapper.select(ApplicationDO::getId, ApplicationDO::getAppName)
                    .eq(ApplicationDO::getAppModel, id)
                    .eq(ApplicationDO::getAppModelType, type);
        }, ApplicationDO::getId, ApplicationDO::getAppName, new Object[]{modelId, appModelType});
    }
}
