package cn.fateverse.query.service.impl;

import cn.fateverse.common.core.exception.CustomException;
import cn.fateverse.common.core.result.page.TableDataInfo;
import cn.fateverse.common.mybatis.utils.PageUtils;
import cn.fateverse.query.entity.*;
import cn.fateverse.query.entity.bo.PortalBo;
import cn.fateverse.query.entity.dto.MockParam;
import cn.fateverse.query.entity.vo.DataAdapterVo;
import cn.fateverse.query.entity.vo.PortalIdWrapper;
import cn.fateverse.query.handler.reader.DataAdapterHandlerReader;
import cn.fateverse.query.portal.service.HandlerMethodService;
import cn.fateverse.query.constant.QueryConstant;
import cn.fateverse.query.entity.dto.DataAdapterDto;
import cn.fateverse.query.entity.dto.PortalDto;
import cn.fateverse.query.entity.query.PortalQuery;
import cn.fateverse.query.entity.vo.PortalVo;
import cn.fateverse.query.entity.vo.SimplePortalVo;
import cn.fateverse.query.enums.PortalEnum;
import cn.fateverse.query.mapper.DataAdapterMapper;
import cn.fateverse.query.mapper.PortalMapper;
import cn.fateverse.query.mapper.PortalMappingMapper;
import cn.fateverse.query.mapper.UniQueryMapper;
import cn.fateverse.query.service.PortalService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Clay
 * @date 2024/4/15 10:31
 */
@Slf4j
@Service
public class PortalServiceImpl implements PortalService {


    @Resource
    private RedisTemplate<String, PortalBo> redisTemplate;

    private final HandlerMethodService methodService;

    private final PortalMapper portalMapper;

    private final UniQueryMapper queryMapper;

    private final DataAdapterMapper adapterMapper;

    private final PortalMappingMapper portalMappingMapper;


    private final DataAdapterHandlerReader handlerReader;

    public PortalServiceImpl(PortalMapper portalMapper,
                             UniQueryMapper queryMapper,
                             DataAdapterMapper adapterMapper,
                             PortalMappingMapper portalMappingMapper,
                             ThreadPoolTaskExecutor taskExecuteExecutor,
                             HandlerMethodService methodService,
                             DataAdapterHandlerReader handlerReader) {
        this.portalMapper = portalMapper;
        this.queryMapper = queryMapper;
        this.adapterMapper = adapterMapper;
        this.portalMappingMapper = portalMappingMapper;
        this.methodService = methodService;
        this.handlerReader = handlerReader;
        taskExecuteExecutor.execute(() -> {
            PortalQuery query = new PortalQuery();
//            query.setState(QueryConstant.PORTAL_PUBLISH);
            List<Portal> portalList = portalMapper.selectList(query);
            if (ObjectUtils.isEmpty(portalList)) {
                log.info("portal is empty!");
                return;
            }
            List<Long> portalIds = portalList.stream().map(Portal::getPortalId).collect(Collectors.toList());
            if (ObjectUtils.isEmpty(portalIds)) {
                log.info("portalIds is empty!");
                return;
            }
            List<PortalMapping> portalMappingList = portalMappingMapper.selectByPortalIds(portalIds);
            Map<Long, List<PortalMapping>> portalMappingMap = new HashMap<>();
            if (!ObjectUtils.isEmpty(portalMappingList)) {
                portalMappingMap = portalMappingList.stream().collect(Collectors.groupingBy(PortalMapping::getPortalId));
            }
            List<PortalMapping> emptyPortalMapping = new ArrayList<>();
            for (Portal portal : portalList) {
                try {
                    publishPortal(portal, portalMappingMap.getOrDefault(portal.getPortalId(), emptyPortalMapping), Boolean.FALSE);
                } catch (Exception e) {
                    if (e instanceof CustomException) {
                        log.error(e.getMessage());
                    } else {
                        log.error("portalId:{} , portalName:{} publish error", portal.getPortalId(), portal.getPortalName(), e);
                    }
                    redisTemplate.delete(QueryConstant.PORTAL_KEY + portal.getPath() + ":" + portal.getRequestMethod());
                }
            }
        });
    }

    @Override
    public PortalVo searchById(Long portalId) {
        Portal portal = portalMapper.selectById(portalId);
        PortalVo portalVo = PortalVo.toPortalVo(portal);
        List<PortalMapping> portalMappings = portalMappingMapper.selectByPortalId(portalId);
        if (ObjectUtils.isEmpty(portalMappings)) {
            portalMappings = new ArrayList<>();
        }
        if (PortalEnum.LOCAL.equals(portal.getType())) {
            UniQuery uniQuery = queryMapper.selectById(portal.getQueryId());
            portalVo.setQueryType(uniQuery.getType());
        }
        portalVo.setMappings(portalMappings);
        return portalVo;
    }

    @Override
    public PortalVo searchDetailById(Long portalId) {
        PortalVo portalVo = searchById(portalId);
        if (!ObjectUtils.isEmpty(portalVo.getAdapterId()) && portalVo.getCreateDataAdapter()) {
            DataAdapter dataAdapter = adapterMapper.selectById(portalVo.getAdapterId());
            if (ObjectUtils.isEmpty(dataAdapter)) {
                return portalVo;
            }
            DataAdapterVo dataAdapterVo = DataAdapterVo.toDataAdapterVo(dataAdapter);
            portalVo.setDataAdapter(dataAdapterVo);
        }
        return portalVo;
    }

    @Override
    public TableDataInfo<SimplePortalVo> searchList(PortalQuery query) {
        PageUtils.startPage();
        List<Portal> list = portalMapper.selectList(query);
        if (ObjectUtils.isEmpty(list)) {
            return PageUtils.convertDataTable(null, 0L);
        }
        List<Long> queryIds = new ArrayList<>();
        List<Long> adapterIds = new ArrayList<>();
        list.forEach(portal -> {
            if (!ObjectUtils.isEmpty(portal.getQueryId())) {
                queryIds.add(portal.getQueryId());
            }
            if (!ObjectUtils.isEmpty(portal.getAdapterId())) {
                adapterIds.add(portal.getAdapterId());
            }
        });
        //映射自定义擦洗名称
        Map<Long, UniQuery> queryMap = new HashMap<>();
        if (!ObjectUtils.isEmpty(queryIds)) {
            List<UniQuery> queryList = queryMapper.selectListByIds(queryIds);
            for (UniQuery uniQuery : queryList) {
                queryMap.put(uniQuery.getId(), uniQuery);
            }
        }
        //映射数据适配器名称
        Map<Long, String> adapterMap = new HashMap<>();
        if (!ObjectUtils.isEmpty(adapterIds)) {
            List<DataAdapter> adapterList = adapterMapper.selectListByIds(adapterIds);
            for (DataAdapter adapter : adapterList) {
                adapterMap.put(adapter.getAdapterId(), adapter.getAdapterName());
            }
        }
        //映射重组
        return PageUtils.convertDataTable(list, portal -> {
            SimplePortalVo simplePortalVo = SimplePortalVo.toPortalVo(portal);
            UniQuery uniQuery = queryMap.getOrDefault(portal.getQueryId(), null);
            if (!ObjectUtils.isEmpty(uniQuery)) {
                simplePortalVo.setQueryName(uniQuery.getUqName());
                simplePortalVo.setQueryType(uniQuery.getType());
            }
            simplePortalVo.setAdapterName(adapterMap.getOrDefault(portal.getAdapterId(), null));
            return simplePortalVo;
        });
    }

    @Override
    public Object mockExecute(MockParam mockParam) {
        Portal portal = portalMapper.selectById(mockParam.getPortalId());
        PortalBo portalBo = PortalBo.toPortalBo(portal, null);
        return handlerReader.mockExecute(portalBo, mockParam);
    }


    @Override
    public Object mockData(MockParam mockParam) {
        Portal portal = portalMapper.selectById(mockParam.getPortalId());
        PortalBo portalBo = PortalBo.toPortalBo(portal, null);
        portalBo.setCreateDataAdapter(false);
        return handlerReader.mockExecute(portalBo, mockParam);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PortalIdWrapper save(PortalDto portalDto) {
        Portal portal = portalDto.toPortal();
        checkPortalType(portal);
        Portal old = portalMapper.selectByPath(portal.getPath(), portal.getRequestMethod());
        if (!ObjectUtils.isEmpty(old)) {
            throw new CustomException("系统中存在当前请求路径");
        }
        UniQuery uniQuery = queryMapper.selectById(portal.getQueryId());
        if (ObjectUtils.isEmpty(uniQuery)) {
            throw new CustomException("未找到对应的查询接口!");
        }
        portal.setState(QueryConstant.PORTAL_DEV);
        // 判断是否需要创建数据适配器
        if (portal.getCreateDataAdapter()) {
            createDataAdapter(portalDto, portal);
        } else {
            DataAdapter dataAdapter = adapterMapper.selectById(portal.getAdapterId());
            if (ObjectUtils.isEmpty(dataAdapter)) {
                throw new CustomException("未找到对应的数据适配器!");
            }
        }
        portalMapper.insert(portal);
        List<PortalMapping> mappings = portalDto.getMappings();
        if (!ObjectUtils.isEmpty(mappings)) {
            for (PortalMapping mapping : mappings) {
                mapping.setPortalId(portal.getPortalId());
            }
            portalMappingMapper.insertBatch(mappings);
        }
        //发布接口
        publishPortal(portal, mappings, Boolean.TRUE);
        return PortalIdWrapper.builder()
                .portalId(String.valueOf(portal.getPortalId()))
                .adapterId(String.valueOf(portal.getAdapterId()))
                .build();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PortalIdWrapper edit(PortalDto portalDto) {
        Portal portal = portalDto.toPortal();
        checkPortalType(portal);
        Portal old = portalMapper.selectByPath(portal.getPath(), portal.getRequestMethod());
        if (!ObjectUtils.isEmpty(old) && !old.getPortalId().equals(portal.getPortalId())) {
            throw new CustomException("系统中存在当前请求路径");
        }
        if (portal.getCreateDataAdapter() != old.getCreateDataAdapter()) {
            if (portal.getCreateDataAdapter()) {
                createDataAdapter(portalDto, portal);
            } else {
                adapterMapper.deleteById(old.getAdapterId());
            }
        }
        portalMappingMapper.deleteByPortalId(portal.getPortalId());
        portalMapper.update(portal);
        if (!ObjectUtils.isEmpty(portalDto.getMappings())) {
            List<PortalMapping> mappings = portalDto.getMappings();
            for (PortalMapping mapping : mappings) {
                mapping.setPortalId(portal.getPortalId());
            }
            portalMappingMapper.insertBatch(mappings);
        }
        //返回接口id和适配器id
        return PortalIdWrapper.builder()
                .portalId(String.valueOf(portal.getPortalId()))
                .adapterId(String.valueOf(portal.getAdapterId()))
                .build();
    }

    @Override
    public void cancel(Long portalId) {
        Portal portal = portalMapper.selectById(portalId);
        if (ObjectUtils.isEmpty(portal)) {
            return;
        }
        if (QueryConstant.PORTAL_DEV.equals(portal.getState())) {
            throw new CustomException("当前状态错误");
        }
        portalMapper.updateState(portalId, QueryConstant.PORTAL_DEV);
        if (QueryConstant.PORTAL_PUBLISH.equals(portal.getState())) {
            unpublishPortal(portal, Boolean.TRUE);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long portalId) {
        Portal portal = portalMapper.selectById(portalId);
        if (ObjectUtils.isEmpty(portal)) {
            return;
        }
        if (!QueryConstant.PORTAL_DEV.equals(portal.getState())) {
            throw new CustomException("当前状态不允许删除");
        }
        if (portal.getCreateDataAdapter()) {
            adapterMapper.deleteById(portal.getAdapterId());
        }
        portalMappingMapper.deleteByPortalId(portalId);
        portalMapper.deleteById(portalId);
    }


    /**
     * 发布接口
     *
     * @param portal   接口信息
     * @param mappings 映射信息
     * @param sync     是否同步
     */
    private void publishPortal(Portal portal, List<PortalMapping> mappings, Boolean sync) {
        PortalBo portalBo = PortalBo.toPortalBo(portal, mappings);
        redisTemplate.opsForValue().set(QueryConstant.PORTAL_KEY + portalBo.getPath() + ":" + portalBo.getRequestMethod(), portalBo);
        methodService.registerMapping(portalBo.getPath(), RequestMethod.valueOf(portalBo.getRequestMethod()), sync);
    }

    /**
     * 取消接口映射
     *
     * @param portal 接口信息
     * @param sync   是否同步
     */
    private void unpublishPortal(Portal portal, Boolean sync) {
        methodService.unregisterMapping(portal.getPath(), RequestMethod.valueOf(portal.getRequestMethod()), sync);
        redisTemplate.delete(QueryConstant.PORTAL_KEY + portal.getPath() + ":" + portal.getRequestMethod());
    }

    /**
     * 创建数据适配器
     *
     * @param portalDto 接口dto对象
     * @param portal    接口信息
     */
    private void createDataAdapter(PortalDto portalDto, Portal portal) {
        DataAdapterDto dataAdapterDto = portalDto.getDataAdapter();
        if (ObjectUtils.isEmpty(dataAdapterDto)) {
            throw new CustomException("数据适配器为空!");
        }
        DataAdapter dataAdapter = dataAdapterDto.toDataAdapter();
        if (ObjectUtils.isEmpty(dataAdapter.getType())) {
            throw new CustomException("请选择数据适配器类型!");
        }
        dataAdapter.setAdapterName(portal.getPortalName() + "专用");
        dataAdapter.setCommon(Boolean.FALSE);
        //初始化对应的数据适配器
        dataAdapter.init();
        //插入数据适配器
        adapterMapper.insert(dataAdapter);
        //会写数据适配器id
        portal.setAdapterId(dataAdapter.getAdapterId());
    }

    /**
     * 检查接口类型是否正确
     *
     * @param portal 接口信息
     */
    private void checkPortalType(Portal portal) {
        if (PortalEnum.LOCAL.equals(portal.getType())) {
            if (ObjectUtils.isEmpty(portal.getQueryId())) {
                throw new CustomException("请选择查询接口!");
            }
            portal.setUrl(null);
        } else if (PortalEnum.EXTERNAL.equals(portal.getType())) {
            if (ObjectUtils.isEmpty(portal.getUrl())) {
                throw new CustomException("第三方接口地址不能为空!");
            }
        } else {
            throw new CustomException("当前类型不支持");
        }
    }

}
