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

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
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.entity.EsBaseEnterpriseInfo;
import com.yeejoin.amos.boot.module.common.api.entity.TzsDataRefreshMessage;
import com.yeejoin.amos.boot.module.common.biz.refresh.DataRefreshEvent;
import com.yeejoin.amos.boot.module.tcm.api.entity.TzBaseEnterpriseInfo;
import com.yeejoin.amos.boot.module.tcm.biz.refresh.handler.EnterpriseRefreshHandler;
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 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.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
public class EnterpriseManageServiceImpl {
    private final TzsUserInfoServiceImpl userInfoService;

    private final RestHighLevelClient restHighLevelClient;

    private final TzBaseEnterpriseInfoServiceImpl enterpriseInfoService;

    private final EsBaseEnterpriseInfoDao esBaseEnterpriseInfoDao;

    private final EnterpriseRefreshHandler refreshHandler;

    /**
     * 批量删除企业
     *
     * @param ids 企业ids
     */
    public Boolean 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);
        });
        return true;
    }

    private List<String> castIds2UseUnitCodes(List<String> ids, List<TzBaseEnterpriseInfo> enterpriseInfos) {
        List<String> useUnitCodes = new ArrayList<>();
        Map<String, String> map = enterpriseInfos.stream().collect(Collectors.toMap(s -> String.valueOf(s.getSequenceNbr()), TzBaseEnterpriseInfo::getUseUnitCode));
        ids.forEach(id -> {
            useUnitCodes.add(map.get(id));
        });
        return useUnitCodes;
    }

    /**
     * 批量启用企业
     *
     * @param ids 企业ids
     */
    public Boolean enable(List<String> ids) {
        // 校验
        List<TzBaseEnterpriseInfo> enterpriseInfos = enterpriseInfoService.getBaseMapper().selectBatchIds(ids);
        // 状态校验，非停用状态不能启用
        this.checkForEnable(ids, enterpriseInfos);
        enterpriseInfos.forEach(enterpriseInfo -> {
            this.updateEnterpriseDeletedStatus(enterpriseInfo, false);
            this.insertEnterprise2Es(enterpriseInfo);
            this.unlockLoginAccount(enterpriseInfo);
        });
        return true;
    }


    /**
     * 批量停用企业
     *
     * @param ids 企业ids
     */
    public Boolean disable(List<String> ids) {
        // 校验
        List<TzBaseEnterpriseInfo> enterpriseInfos = enterpriseInfoService.getBaseMapper().selectBatchIds(ids);
        // 状态校验，非启用状态不能停用
        this.checkForDisable(ids, enterpriseInfos);
        enterpriseInfos.forEach(enterpriseInfo -> {
            this.updateEnterpriseDeletedStatus(enterpriseInfo, true);
            this.deleteEnterpriseFromEs(enterpriseInfo);
            this.lockLoginAccount(enterpriseInfo);
        });
        return true;
    }


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

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

    private void deleteEnterpriseFromEs(TzBaseEnterpriseInfo enterpriseInfo) {
        esBaseEnterpriseInfoDao.deleteById(enterpriseInfo.getSequenceNbr() + "");
    }

    private void lockLoginAccount(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.lockUsers(String.join(",", userIds));
        }
    }


    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 deleteEsEnterpriseInfo(TzBaseEnterpriseInfo e) {
        Optional<EsBaseEnterpriseInfo> op = esBaseEnterpriseInfoDao.findById(e.getSequenceNbr() + "");
        op.ifPresent(v -> {
            esBaseEnterpriseInfoDao.deleteById(v.getSequenceNbr());
        });
    }

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

    private void deleteEnterprise(TzBaseEnterpriseInfo e) {
        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));
    }
}
