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

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import com.yeejoin.amos.boot.biz.common.bo.ReginParams;
import com.yeejoin.amos.boot.biz.common.dto.CountDto;
import com.yeejoin.amos.boot.module.common.api.constant.TZSCommonConstant;
import com.yeejoin.amos.boot.module.common.api.dto.DPFilterParamDto;
import com.yeejoin.amos.boot.module.common.api.dto.DPFilterParamForDetailDto;
import com.yeejoin.amos.boot.module.common.api.enums.ReginStepEnum;
import com.yeejoin.amos.boot.module.jg.api.enums.CylinderTypeEnum;
import com.yeejoin.amos.boot.module.jg.api.enums.DPMapStatisticsItemEnum;
import com.yeejoin.amos.boot.module.statistics.api.mapper.CommonBaseMapper;
import com.yeejoin.amos.boot.module.statistics.api.mapper.ZLStatisticsMapper;
import com.yeejoin.amos.boot.module.statistics.api.vo.EquCategoryVo;
import com.yeejoin.amos.boot.module.ymt.api.dto.EquipmentCategoryDto;
import com.yeejoin.amos.boot.module.ymt.api.enums.EquipmentClassifityEnum;
import com.yeejoin.amos.boot.module.ymt.api.mapper.EquipTechParamPipelineMapper;
import com.yeejoin.amos.boot.module.ymt.api.mapper.EquipmentCategoryMapper;
import com.yeejoin.amos.feign.systemctl.Systemctl;
import com.yeejoin.amos.feign.systemctl.model.RegionModel;
import org.apache.lucene.queryparser.classic.QueryParser;
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.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
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 java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
@Service
public class StCommonServiceImpl {

    /**
     * 气瓶设备类别
     */
    public final static String EQU_CATEGORY_CYLINDER = "2300";


    /**
     * 行政区划级别-3级
     */
    public final static String REGION_LEVEL_THIRD = "3";

    private CommonBaseMapper commonMapper;

    private RestHighLevelClient restHighLevelClient;

    private EquipTechParamPipelineMapper techParamsPipelineMapper;

    private EquipmentCategoryMapper equipmentCategoryMapper;

    private ZLStatisticsMapper zlStatisticsMapper;

    private static Map<String, String> regionCodeOrgCodeMap = new ConcurrentHashMap<>();

    private static Map<Integer, RegionModel> regionCodeRegionMap = new ConcurrentHashMap<>();

    private static List<RegionModel> regionModels = new ArrayList<>();

    private static List<EquipmentCategoryDto> equipmentCategoryDtos;

    public StCommonServiceImpl(CommonBaseMapper commonMapper, RestHighLevelClient restHighLevelClient, EquipTechParamPipelineMapper techParamsPipelineMapper, EquipmentCategoryMapper equipmentCategoryMapper, ZLStatisticsMapper zlStatisticsMapper) {
        this.commonMapper = commonMapper;
        this.restHighLevelClient = restHighLevelClient;
        this.techParamsPipelineMapper = techParamsPipelineMapper;
        this.equipmentCategoryMapper = equipmentCategoryMapper;
        this.zlStatisticsMapper = zlStatisticsMapper;
    }

    public static Map<String, String> getRegionCodeOrgCodeMap() {
        return regionCodeOrgCodeMap;
    }

    public static List<EquipmentCategoryDto> getEquipmentCategory() {
        return equipmentCategoryDtos;
    }

    public void init() {
        // 数据不变所以放到内存，提高响应时间
        equipmentCategoryDtos = equipmentCategoryMapper.selectClassify();
        initReginCode();
    }

    public List<RegionModel> getRegionModels(){
        return regionModels;
    }

    private void initReginCode() {
        Collection<RegionModel> result = Systemctl.regionClient.queryForTree(null).getResult();
        result.forEach(r -> {
            regionModels.add(r);
            this.loopSetChildRegin(regionModels, r.getChildren());
        });
        regionCodeRegionMap = regionModels.stream().collect(Collectors.toMap(RegionModel::getRegionCode, Function.identity(), (k1, k2) -> k2));
    }

    private void loopSetChildRegin(List<RegionModel> regionModels, Collection<RegionModel> children) {
        if (children != null && children.size() > 0) {
            children.forEach(c -> {
                regionModels.add(c);
                this.loopSetChildRegin(regionModels, c.getChildren());
            });
        }
    }

    public String getAndSetOrgCode(DPFilterParamDto dpFilterParamDto) {
        String cityCode = dpFilterParamDto.getCityCode();
        return getAndSetOrgCode(cityCode);
    }

    public String getAndSetOrgCode(String cityCode) {
        String orgCode = regionCodeOrgCodeMap.get(cityCode);
        if (orgCode == null) {
            orgCode = commonMapper.getOrgCodeByCompanyCode(cityCode);
            if (orgCode != null) {
                regionCodeOrgCodeMap.put(cityCode, orgCode);
            }
        }
        return orgCode;
    }

    public List<RegionModel> getUserRegionCode(ReginParams selectedOrgInfo) {
        String regionCode = selectedOrgInfo.getCompany().getCompanyCode();
        Optional<RegionModel> op = regionModels.stream().filter(e -> e.getRegionCode().toString().equals(regionCode)).findFirst();
        RegionModel model = op.orElse(new RegionModel());
        RegionModel result = new RegionModel();
        result.setRegionName(model.getRegionName());
        result.setRegionCode(Integer.parseInt(regionCode));
        return Collections.singletonList(result);
    }

    public List<RegionModel> setRegionIfRootParent(DPFilterParamDto dpFilterParamDto) {
        String regionCode = dpFilterParamDto.getCityCode();
        return setRegionIfRootParent(regionCode);
    }

    /**
     * 获取指定区域下的子区域
     *
     * @param regionCode 行政区划
     * @return List<RegionModel>
     */
    public List<RegionModel> setRegionIfRootParent(String regionCode) {
        List<RegionModel> regions = regionModels.parallelStream().filter(e -> e.getParentRegionCode() != null && (e.getParentRegionCode().toString()).equals(regionCode)).collect(Collectors.toList());
        // 陕西省时需要在地图返回独立的地级市:韩城、杨凌、西咸
        if (regionCode.equals(TZSCommonConstant.SHAN_XI_REGION_CODE)) {
            List<RegionModel> independentRegions = ReginStepEnum.enum2RegionList("map");
            regions.addAll(independentRegions);
        }
        return regions;
    }

    /**
     * 获取指定区域下的子区域，独立地市及区县级不再下钻，直接返回当前区域信息
     *
     * @param dpFilterParamDto 行政区划过滤条件
     * @return List<RegionModel>
     */
    public List<RegionModel> setRegionIfRootParentAndNoAccessIf3Level(DPFilterParamDto dpFilterParamDto) {
        String regionCode = dpFilterParamDto.getCityCode();
        return setRegionIfRootParentAndNoAccessIf3Level(regionCode);
    }


    /**
     * 获取指定区域下的子区域，独立地市及区县级不再下钻，直接返回当前区域信息
     *
     * @param regionCode 行政区划
     * @return List<RegionModel>
     */
    public List<RegionModel> setRegionIfRootParentAndNoAccessIf3Level(String regionCode) {
        if (regionCodeRegionMap.get(Integer.parseInt(regionCode)) != null && regionCodeRegionMap.get(Integer.parseInt(regionCode)).getLevel().trim().equals(REGION_LEVEL_THIRD)) {
            // 区县级别时（level=3），直接返回当前区域信息，不在显示街道
            return Collections.singletonList(regionCodeRegionMap.get(Integer.parseInt(regionCode)));
        } else if ((Arrays.stream(ReginStepEnum.values())).anyMatch(e -> e.getCode().equals(regionCode))) {
            // 独立地市，直接返回当前区域信息，不在显示街道
            return ReginStepEnum.getOne(regionCode);
        } else {
            return setRegionIfRootParent(regionCode);
        }
    }

    public List<String> buildXData(List<RegionModel> regionList) {
        return regionList.stream().map(RegionModel::getRegionName).collect(Collectors.toList());
    }

    public long staticsCenterMapCountDataForCylinder(Map<String, Object> result, String orgCode) {
        long num = 0;
        CountRequest request = new CountRequest();
        request.indices("idx_biz_view_jg_all");
        BoolQueryBuilder boolMust = QueryBuilders.boolQuery();
        // 按照管辖机构区域信息模糊查询
        boolMust.must(QueryBuilders.wildcardQuery("ORG_BRANCH_CODE.keyword", QueryParser.escape(orgCode) + "*"));
        // 设备类别精确查询气瓶
        boolMust.must(QueryBuilders.termQuery("EQU_CATEGORY_CODE", EQU_CATEGORY_CYLINDER));
        // 只统计已纳管设备
        boolMust.must(QueryBuilders.termQuery("IS_INTO_MANAGEMENT", Boolean.TRUE));
        // 只统计有监管码的、状态不是草稿且不是已拒领的
        boolMust.must(QueryBuilders.existsQuery("SUPERVISORY_CODE"));
        boolMust.mustNot(QueryBuilders.termQuery("STATUS","草稿"));
        boolMust.mustNot(QueryBuilders.termQuery("STATUS","已拒领"));
        request.query(boolMust);
        try {
            CountResponse response = restHighLevelClient.count(request, RequestOptions.DEFAULT);
            num = response.getCount();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        result.put(DPMapStatisticsItemEnum.GAS.getCode(), num);
        return num;
    }

    public void staticsCenterMapCountDataForEquip(Map<String, Object> result, long cylinderNum, String orgCode) {
        SearchRequest request = new SearchRequest();
        request.indices("idx_biz_view_jg_all");
        BoolQueryBuilder boolMust = QueryBuilders.boolQuery();
        // 按照管辖机构区域信息模糊查询
        boolMust.must(QueryBuilders.wildcardQuery("ORG_BRANCH_CODE.keyword", QueryParser.escape(orgCode) + "*"));
        // 只统计已纳管设备
        boolMust.must(QueryBuilders.termQuery("IS_INTO_MANAGEMENT", Boolean.TRUE));
        // 只统计有监管码的、状态不是草稿且不是已拒领的
        boolMust.must(QueryBuilders.existsQuery("SUPERVISORY_CODE"));
        boolMust.mustNot(QueryBuilders.termQuery("STATUS","草稿"));
        boolMust.mustNot(QueryBuilders.termQuery("STATUS","已拒领"));
        // 且8大类，目的去掉脏数据
        boolMust.must(QueryBuilders.termsQuery("EQU_LIST_CODE",StCommonServiceImpl.getEquipmentCategory().stream().map(EquipmentCategoryDto::getCode).collect(Collectors.toList())));
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(boolMust);
        // 默认size为10 ，数据库有脏数据，所以需要多查询一些
        TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("count_by_equ_list_code").field("EQU_LIST_CODE").size(20);
        builder.aggregation(aggregationBuilder);
        request.source(builder);
        try {
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            Terms terms = response.getAggregations().get("count_by_equ_list_code");
            Map<String, Long> countMap = new HashMap<>();
            for (Terms.Bucket bucket : terms.getBuckets()) {
                // 是八大类且排除掉脏数据[数据库存在equ_list_code汉字及其他]
                if (equipmentCategoryDtos.stream().anyMatch(e -> e.getCode().equalsIgnoreCase(bucket.getKeyAsString()))) {
                    // 压力容器里包括气瓶所以需要特殊处理，在统计压力容器时去掉气瓶的数量
                    if(bucket.getKeyAsString().equals(EquipmentClassifityEnum.YLRQ.getCode())){
                        countMap.put(bucket.getKeyAsString(), bucket.getDocCount() - cylinderNum);
                    } else if(!bucket.getKeyAsString().equals(EquipmentClassifityEnum.YLGD.getCode())) {
                        //  压力管道单独统计，求总数时，不包括压力管道
                        countMap.put(bucket.getKeyAsString(), bucket.getDocCount());
                    }
                }
            }
            // 按照8大类枚举，进行加工。目的：固定八大类防止没统计数据导致缺少分类、将设备种类的code换成前端定义的key
            equipmentCategoryDtos.forEach(c -> {
                result.put(this.castCategoryCode2WebCode(c.getCode()), countMap.getOrDefault(c.getCode(), 0L));
            });
            // 注意，求总数时：countMap不包括气瓶数量、压力管道数量（20240819修改）
            result.put(DPMapStatisticsItemEnum.TOTAL.getCode(), countMap.values().stream().mapToLong(e -> e).sum());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        result.remove(DPMapStatisticsItemEnum.PRESSURE_PIPELINES.getCategory());
    }

    private String castCategoryCode2WebCode(String category) {
        DPMapStatisticsItemEnum itemEnum = DPMapStatisticsItemEnum.getInstanceByCategory(category);
        return itemEnum.getCode();
    }

    public void staticsCenterMapCountDataForPipeline(Map<String, Object> result, String orgCode) {
        String length = techParamsPipelineMapper.sumPipeLengthByOrgCode(orgCode);
        BigDecimal lengthDecimal = new BigDecimal(length);
        if (lengthDecimal.compareTo(BigDecimal.ZERO) > 0) {
            // 数据库的米换算成千米
            length = lengthDecimal.divide(new BigDecimal("1000"), 3, RoundingMode.HALF_UP).toPlainString();
        }
        result.put(DPMapStatisticsItemEnum.PRESSURE_PIPELINES.getCode(), length);
    }

    public CountDto getRedStatusCompany(DPFilterParamDto dpFilterParamDto) {
        CountDto countDto = new CountDto();
        countDto.setLongValue(this.countCompanyForCertDateTimeOut(dpFilterParamDto));
        countDto.setLabel("许可超期");
        return countDto;
    }

    public CountDto getYellowStatusCompany(DPFilterParamDto dpFilterParamDto) {
        CountDto countDto = new CountDto();
        countDto.setLongValue(this.countCompanyForCertDateTemporary(dpFilterParamDto));
        countDto.setLabel("许可临期");
        return countDto;
    }


    private long countCompanyForCertDateTemporary(DPFilterParamDto dpFilterParamDto) {
        String orgCode = this.getAndSetOrgCode(dpFilterParamDto.getCityCode());
        // 临期6个月
        String limitDate = DateUtil.offset(DateUtil.date(), DateField.DAY_OF_MONTH, 6).toDateStr();
        return zlStatisticsMapper.countCompanyForCertDateTemporary(dpFilterParamDto, orgCode, limitDate);
    }

    private long countCompanyForCertDateTimeOut(DPFilterParamDto dpFilterParamDto) {
        String orgCode = this.getAndSetOrgCode(dpFilterParamDto.getCityCode());
        String limitDate = DateUtil.today();
        return zlStatisticsMapper.countCompanyForCertDateTimeOut(dpFilterParamDto, orgCode, limitDate);
    }

    public EquCategoryVo getEquCategoryTree(DPFilterParamForDetailDto paramDto) {
        final List<EquipmentCategoryDto> equipmentCategoryList = getEquipmentCategory();
        List<EquipmentCategoryDto> updatedCategoryList = addSpecialCategory(equipmentCategoryList);
        return createEquCategoryVo(updatedCategoryList);
    }

    /**
     * 向设备分类列表中添加特定类别
     * @param equipmentCategoryList 原始设备分类列表
     * @return 添加了特定类别的新列表
     */
    private List<EquipmentCategoryDto> addSpecialCategory(List<EquipmentCategoryDto> equipmentCategoryList) {
        List<EquipmentCategoryDto> newList = new ArrayList<>(equipmentCategoryList);
        EquipmentCategoryDto specialCategory = new EquipmentCategoryDto();
        specialCategory.setName("气瓶");
        specialCategory.setCode(CylinderTypeEnum.CYLINDER.getCode());
        newList.add(specialCategory);
        return newList;
    }

    /**
     * 创建设备类别树的根节点
     * @param equipmentCategoryList 设备分类数据传输对象列表
     * @return 根节点的 EquCategoryVo 对象
     */
    private EquCategoryVo createEquCategoryVo(List<EquipmentCategoryDto> equipmentCategoryList) {
        List<EquCategoryVo> children = equipmentCategoryList.stream()
                .map(dto -> new EquCategoryVo(dto.getName(), dto.getCode(), null))
                .collect(Collectors.toList());
        return new EquCategoryVo("全部设备",null, children);
    }
}
