package com.yeejoin.amos.boot.module.tcm.biz.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.yeejoin.amos.boot.biz.common.bo.ReginParams;
import com.yeejoin.amos.boot.biz.common.dto.CountDto;
import com.yeejoin.amos.boot.biz.common.entity.BaseEntity;
import com.yeejoin.amos.boot.module.common.api.dao.EsBaseEnterpriseInfoDao;
import com.yeejoin.amos.boot.module.common.api.dao.EsUserInfoDao;
import com.yeejoin.amos.boot.module.common.api.entity.BaseEnterpriseCert;
import com.yeejoin.amos.boot.module.common.api.entity.EsBaseEnterpriseInfo;
import com.yeejoin.amos.boot.module.common.api.entity.TzsDataRefreshMessage;
import com.yeejoin.amos.boot.module.common.api.mapper.BaseEnterpriseCertMapper;
import com.yeejoin.amos.boot.module.common.biz.refresh.DataRefreshEvent;
import com.yeejoin.amos.boot.module.tcm.api.entity.BaseUnitLicence;
import com.yeejoin.amos.boot.module.tcm.api.entity.TzBaseEnterpriseInfo;
import com.yeejoin.amos.boot.module.tcm.api.entity.TzsBaseIndividuality;
import com.yeejoin.amos.boot.module.tcm.api.entity.TzsBaseInstitution;
import com.yeejoin.amos.boot.module.tcm.api.enums.EnterpriseEnums;
import com.yeejoin.amos.boot.module.tcm.api.mapper.BaseUnitLicenceMapper;
import com.yeejoin.amos.boot.module.tcm.api.mapper.TzsBaseIndividualityMapper;
import com.yeejoin.amos.boot.module.tcm.api.mapper.TzsBaseInstitutionMapper;
import com.yeejoin.amos.boot.module.tcm.biz.refresh.handler.EnterpriseRefreshHandler;
import com.yeejoin.amos.boot.module.tcm.flc.api.entity.RegUnitInfo;
import com.yeejoin.amos.boot.module.tcm.flc.api.mapper.RegUnitInfoMapper;
import com.yeejoin.amos.component.feign.utils.FeignUtil;
import com.yeejoin.amos.feign.privilege.Privilege;
import com.yeejoin.amos.feign.privilege.model.AgencyUserModel;
import com.yeejoin.amos.feign.privilege.model.CompanyModel;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.typroject.tyboot.core.restful.exception.instance.BadRequest;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@RequiredArgsConstructor
@Slf4j
public class EnterpriseManageServiceImpl {
    private final TzsUserInfoServiceImpl userInfoService;

    private final RestHighLevelClient restHighLevelClient;

    private final TzBaseEnterpriseInfoServiceImpl enterpriseInfoService;

    private final EsBaseEnterpriseInfoDao esBaseEnterpriseInfoDao;

    private final EnterpriseRefreshHandler refreshHandler;

    private final EsUserInfoDao esUserInfoDao;

    private final TzsBaseInstitutionMapper tzsBaseInstitutionMapper;

    private final TzsBaseIndividualityMapper tzsBaseIndividualityMapper;

    private final BaseEnterpriseCertMapper enterpriseCertMapper;

    private final BaseUnitLicenceMapper unitLicenceMapper;

    private final RegUnitInfoMapper regUnitInfoMapper;

    private final TcmCommonService commonService;


    /**
     * 批量删除企业
     *
     * @param ids 企业ids
     */
    @Transactional(rollbackFor = Exception.class)
    public List<TzBaseEnterpriseInfo> delete(List<String> ids) {
        // 校验
        List<TzBaseEnterpriseInfo> enterpriseInfos = enterpriseInfoService.getBaseMapper().selectBatchIds(ids);
        Map<String, Long> useUnitCodeUserNumMap = this.countUserByUnit(enterpriseInfos);
        this.checkForUse(enterpriseInfos, useUnitCodeUserNumMap, "人员");
        Map<String, Long> useUnitCodeEquipNumMap = this.countEquipByUnit(enterpriseInfos);
        this.checkForUse(enterpriseInfos, useUnitCodeEquipNumMap, "设备");
        // 业务操作
        enterpriseInfos.forEach(e -> {
            this.deleteEnterprise(e);
            this.deleteAmosUser(e);
            this.deleteAmosCompany(e);
            this.deleteEsEnterpriseInfo(e);
            this.deleteEsUser(e);
        });
        return enterpriseInfos;
    }

    /**
     * 冗余删除es的人员（已经前置校验数据库没人、没设备才能删除，目的防止人员的数据库和es人员索引的人员不一致）
     *
     * @param e 企业
     */
    private void deleteEsUser(TzBaseEnterpriseInfo e) {
        esUserInfoDao.deleteAllByUnitCode(e.getUseCode());
    }

    private void deleteEsEnterpriseInfo(TzBaseEnterpriseInfo e) {
        Optional<EsBaseEnterpriseInfo> op = esBaseEnterpriseInfoDao.findById(e.getSequenceNbr() + "");
        op.ifPresent(v -> {
            esBaseEnterpriseInfoDao.deleteById(v.getSequenceNbr());
        });
    }


    /**
     * 批量启用企业
     *
     * @param ids 企业ids
     */
    public ReginParams enable(List<String> ids, JSONObject params, ReginParams reginParams) {
        List<AgencyUserModel> userModelsAll = new ArrayList<>();
        // 校验
        List<TzBaseEnterpriseInfo> enterpriseInfos = enterpriseInfoService.getBaseMapper().selectBatchIds(ids);
        // 状态校验，非停用状态不能启用
        this.checkForEnable(ids, enterpriseInfos);
        enterpriseInfos.forEach(enterpriseInfo -> {
            this.updateEnterpriseDeletedStatus(enterpriseInfo, false);
            // 保留防止之前勿删除了
            this.insertEnterprise2Es(enterpriseInfo);
            List<AgencyUserModel> userModels = this.unlockLoginAccount(enterpriseInfo);
            userModelsAll.addAll(userModels);
        });
        this.buildAndSendSms(userModelsAll, EnterpriseEnums.State.QY, params.getString("reason"), reginParams);
        return reginParams;
    }


    /**
     * 批量停用企业
     *
     * @param ids         企业ids
     * @param params      说明等
     * @param reginParams 操作人的信息
     */
    public ReginParams disable(List<String> ids, JSONObject params, ReginParams reginParams) {
        List<AgencyUserModel> userModelsAll = new ArrayList<>();
        // 校验
        List<TzBaseEnterpriseInfo> enterpriseInfos = enterpriseInfoService.getBaseMapper().selectBatchIds(ids);
        // 状态校验，非启用状态不能停用
        this.checkForDisable(ids, enterpriseInfos);
        enterpriseInfos.forEach(enterpriseInfo -> {
            this.updateEnterpriseDeletedStatus(enterpriseInfo, true);
            List<AgencyUserModel> userModels = this.lockLoginAccount(enterpriseInfo);
            userModelsAll.addAll(userModels);
        });
        this.buildAndSendSms(userModelsAll, EnterpriseEnums.State.TY, params.getString("reason"), reginParams);
        return reginParams;
    }

    private void buildAndSendSms(List<AgencyUserModel> userModelsAll, EnterpriseEnums.State state, String reason, ReginParams reginParams) {
        userModelsAll.forEach(u -> {
            Map<String, String> msg = new HashMap<>();
            msg.put("realName", u.getRealName());
            msg.put("userName", u.getUserName());
            msg.put("companyCode", u.getCompanys().stream().map(CompanyModel::getCompanyCode).collect(Collectors.joining(",")));
            msg.put("companyName", u.getCompanys().stream().map(CompanyModel::getCompanyName).collect(Collectors.joining(",")));
            msg.put("reason", reason);
            msg.put("opUserName", reginParams.getUserModel().getRealName());
            msg.put("opOrgName", reginParams.getCompany().getCompanyName());
            msg.put("opYear", String.valueOf(DateUtil.date().year()));
            msg.put("opMonth", String.valueOf(DateUtil.date().month() + 1));
            msg.put("opDay", String.valueOf(DateUtil.date().dayOfMonth()));
            msg.put("opHour", String.valueOf(DateUtil.date().hour(true)));
            msg.put("opMin", String.valueOf(DateUtil.date().minute()));
            msg.put("unCheckSms", "true");
            commonService.sendSms(getSmsCodeByState(state), u.getMobile(), msg);
        });
    }

    private String getSmsCodeByState(EnterpriseEnums.State state) {
        if (state.equals(EnterpriseEnums.State.QY)) {
            return "SMS_QY_0001";
        } else {
            return "SMS_TY_0001";
        }
    }


    private List<AgencyUserModel> unlockLoginAccount(TzBaseEnterpriseInfo enterpriseInfo) {
        CompanyModel companyModel = FeignUtil.remoteCall(() -> Privilege.companyClient.queryByCompanyCode(enterpriseInfo.getUseCode()));
        List<AgencyUserModel> userModels = FeignUtil.remoteCall(() -> Privilege.agencyUserClient.queryByCompanyId(companyModel.getSequenceNbr(), null, null, false));
        if (!userModels.isEmpty()) {
            List<String> userIds = userModels.stream().map(AgencyUserModel::getUserId).collect(Collectors.toList());
            Privilege.agencyUserClient.unlockUsers(String.join(",", userIds));
        }
        userModels.forEach(e -> e.setCompanys(Collections.singletonList(companyModel)));
        return userModels;
    }

    private void insertEnterprise2Es(TzBaseEnterpriseInfo enterpriseInfo) {
        refreshHandler.doRefresh(new TzsDataRefreshMessage().setDataId(enterpriseInfo.getSequenceNbr() + "").setOperation(DataRefreshEvent.Operation.INSERT.name()));
    }

    private List<AgencyUserModel> lockLoginAccount(TzBaseEnterpriseInfo enterpriseInfo) {
        Set<String> userIds = new HashSet<>();
        CompanyModel companyModel = FeignUtil.remoteCall(() -> Privilege.companyClient.queryByCompanyCode(enterpriseInfo.getUseCode()));
        List<AgencyUserModel> userModels = FeignUtil.remoteCall(() -> Privilege.agencyUserClient.queryByCompanyId(companyModel.getSequenceNbr(), null, null, false));
        if (!userModels.isEmpty()) {
            userIds = userModels.stream().map(AgencyUserModel::getUserId).collect(Collectors.toSet());
            Privilege.agencyUserClient.lockUsers(String.join(",", userIds));
        }
        userModels.forEach(e -> e.setCompanys(Collections.singletonList(companyModel)));
        return userModels;
    }


    private void updateEnterpriseDeletedStatus(TzBaseEnterpriseInfo enterpriseInfo, Boolean isDeleted) {
        LambdaUpdateWrapper<TzBaseEnterpriseInfo> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(BaseEntity::getIsDelete, isDeleted);
        updateWrapper.eq(BaseEntity::getSequenceNbr, enterpriseInfo.getSequenceNbr());
        enterpriseInfoService.update(updateWrapper);
    }

    private void checkForEnable(List<String> ids, List<TzBaseEnterpriseInfo> enterpriseInfos) {
        long enableUnitNum = enterpriseInfos.stream().map(BaseEntity::getIsDelete).filter(d -> !d).count();
        if (enableUnitNum > 0) {
            List<Integer> indexes = new ArrayList<>();
            List<String> enableUnits = enterpriseInfos.stream().filter(s -> !s.getIsDelete()).map(BaseEntity::getSequenceNbr).map(String::valueOf).collect(Collectors.toList());
            for (int i = 0; i < ids.size(); i++) {
                if (enableUnits.contains(ids.get(i))) {
                    indexes.add(i + 1);
                }
            }
            this.buildEnableCheckFailureMsg(indexes);
        }
    }

    private void checkForDisable(List<String> ids, List<TzBaseEnterpriseInfo> enterpriseInfos) {
        long disableUnitNum = enterpriseInfos.stream().map(BaseEntity::getIsDelete).filter(d -> d).count();
        if (disableUnitNum > 0) {
            List<Integer> indexes = new ArrayList<>();
            List<String> disableUnits = enterpriseInfos.stream().filter(BaseEntity::getIsDelete).map(BaseEntity::getSequenceNbr).map(String::valueOf).collect(Collectors.toList());
            for (int i = 0; i < ids.size(); i++) {
                if (disableUnits.contains(ids.get(i))) {
                    indexes.add(i + 1);
                }
            }
            this.buildDisableCheckFailureMsg(indexes);
        }
    }

    private void checkForUse(List<TzBaseEnterpriseInfo> enterpriseInfos, Map<String, Long> seqUseNumMap, String type) {
        if (seqUseNumMap.values().stream().reduce(0L, Long::sum) > 0) {
            List<String> useNames = new ArrayList<>();
            for (TzBaseEnterpriseInfo enterpriseInfo : enterpriseInfos) {
                if (seqUseNumMap.getOrDefault(enterpriseInfo.getUseUnitCode(), 0L) > 0) {
                    useNames.add(enterpriseInfo.getUseUnit());
                }
            }
            this.buildDeleteCheckFailureMsg(useNames, type);
        }
    }

    private void buildDisableCheckFailureMsg(List<Integer> indexDisabled) {
        String msg = indexDisabled.stream().map(String::valueOf).collect(Collectors.joining("、", "您选中的企业：", "，已经是停用状态，不能重复操作！"));
        throw new BadRequest(msg);
    }

    private void buildEnableCheckFailureMsg(List<Integer> indexDisabled) {
        String msg = indexDisabled.stream().map(String::valueOf).collect(Collectors.joining("、", "您选中的企业：", "，已经是启用状态，不能重复操作！"));
        throw new BadRequest(msg);
    }

    private void buildDeleteCheckFailureMsg(List<String> useNames, String type) {
        String msg = String.format("企业：%s下存在%s，请先联系企业删除%s后再操作！", String.join("、", useNames), type, type);
        throw new BadRequest(msg);
    }

    private void deleteAmosCompany(TzBaseEnterpriseInfo e) {
        CompanyModel companyModel = FeignUtil.remoteCall(() -> Privilege.companyClient.queryByCompanyCode(e.getUseCode()));
        Privilege.companyClient.deleteCompany(companyModel.getSequenceNbr() + "");
    }

    private void deleteAmosUser(TzBaseEnterpriseInfo e) {
        CompanyModel companyModel = FeignUtil.remoteCall(() -> Privilege.companyClient.queryByCompanyCode(e.getUseCode()));
        List<AgencyUserModel> userModels = FeignUtil.remoteCall(() -> Privilege.agencyUserClient.queryByCompanyId(companyModel.getSequenceNbr(), null, null, false));
        if (!userModels.isEmpty()) {
            List<String> userIds = userModels.stream().map(AgencyUserModel::getUserId).collect(Collectors.toList());
            Privilege.agencyUserClient.multDeleteUser(String.join(",", userIds), false);
        }
    }

    private void deleteEnterprise(TzBaseEnterpriseInfo e) {
        // 删除关系表
        tzsBaseInstitutionMapper.delete(new LambdaQueryWrapper<TzsBaseInstitution>().eq(TzsBaseInstitution::getEnterpriseId, e.getSequenceNbr()));

        tzsBaseIndividualityMapper.delete(new LambdaQueryWrapper<TzsBaseIndividuality>().eq(TzsBaseIndividuality::getEnterpriseId, e.getSequenceNbr()));

        enterpriseCertMapper.delete(new LambdaQueryWrapper<BaseEnterpriseCert>().eq(BaseEnterpriseCert::getUnitCode, e.getUseUnitCode()));

        unitLicenceMapper.delete(new LambdaQueryWrapper<BaseUnitLicence>().eq(BaseUnitLicence::getUnitCode, e.getUseUnitCode()));

        regUnitInfoMapper.delete(new LambdaQueryWrapper<RegUnitInfo>().eq(RegUnitInfo::getUnitCode, e.getUseUnitCode()));

        // 删除主表
        enterpriseInfoService.removeById(e.getSequenceNbr());
    }

    private Map<String, Long> countEquipByUnit(List<TzBaseEnterpriseInfo> enterpriseInfos) {
        // 按照使用单位查询
        Map<String, Long> useCountMap = countByUseCode(enterpriseInfos);
        // 按照安装单位查询
        Map<String, Long> uscCountMap = countByUscCode(enterpriseInfos);
        return this.mergeUseAndUscCount(useCountMap, uscCountMap);
    }

    private Map<String, Long> mergeUseAndUscCount(Map<String, Long> useCountMap, Map<String, Long> uscCountMap) {
        return Stream.of(useCountMap, uscCountMap).flatMap(map -> map.entrySet().stream()).filter(entry -> entry.getKey() != null && entry.getValue() != null) // 过滤null
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Long::sum,     // key冲突时，求和
                        HashMap::new));
    }

    private Map<String, Long> countByUscCode(List<TzBaseEnterpriseInfo> enterpriseInfos) {
        Map<String, Long> countMap = new HashMap<>();
        SearchRequest request = new SearchRequest();
        request.indices("idx_biz_view_jg_all");
        BoolQueryBuilder boolMust = QueryBuilders.boolQuery();
        boolMust.must(QueryBuilders.termsQuery("USC_UNIT_CREDIT_CODE.keyword", enterpriseInfos.stream().map(TzBaseEnterpriseInfo::getUseUnitCode).collect(Collectors.toList())));
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(boolMust);
        TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("count_by_usc_code").field("USC_UNIT_CREDIT_CODE.keyword");
        builder.aggregation(aggregationBuilder);
        request.source(builder);
        try {
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            Terms terms = response.getAggregations().get("count_by_usc_code");
            for (Terms.Bucket bucket : terms.getBuckets()) {
                countMap.put(bucket.getKeyAsString(), bucket.getDocCount());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return countMap;
    }

    private Map<String, Long> countByUseCode(List<TzBaseEnterpriseInfo> enterpriseInfos) {
        Map<String, Long> countMap = new HashMap<>();
        SearchRequest request = new SearchRequest();
        request.indices("idx_biz_view_jg_all");
        BoolQueryBuilder boolMust = QueryBuilders.boolQuery();
        boolMust.must(QueryBuilders.termsQuery("USE_UNIT_CREDIT_CODE.keyword", enterpriseInfos.stream().map(TzBaseEnterpriseInfo::getUseUnitCode).collect(Collectors.toList())));
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(boolMust);
        TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("count_by_use_code").field("USE_UNIT_CREDIT_CODE.keyword");
        builder.aggregation(aggregationBuilder);
        request.source(builder);
        try {
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            Terms terms = response.getAggregations().get("count_by_use_code");
            for (Terms.Bucket bucket : terms.getBuckets()) {
                countMap.put(bucket.getKeyAsString(), bucket.getDocCount());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return countMap;
    }

    private Map<String, Long> countUserByUnit(List<TzBaseEnterpriseInfo> enterpriseInfos) {
        List<String> unitCodes = enterpriseInfos.stream().map(TzBaseEnterpriseInfo::getUseCode).collect(Collectors.toList());
        List<CountDto> countDos = userInfoService.getBaseMapper().countUserByUnitCodes(unitCodes);
        return countDos.stream().collect(Collectors.toMap(CountDto::getKeyStr, CountDto::getLongValue));
    }
}
