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

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yeejoin.amos.boot.biz.common.dto.CountDto;
import com.yeejoin.amos.boot.module.common.api.dto.CylinderFillingRecordStatisticsDto;
import com.yeejoin.amos.boot.module.common.api.dto.DPFilterParamDto;
import com.yeejoin.amos.boot.module.statistics.api.mapper.CylinderStatisticsMapper;
import com.yeejoin.amos.boot.module.ymt.api.entity.TzBaseEnterpriseInfo;
import com.yeejoin.amos.boot.module.ymt.api.enums.EquimentEnum;
import com.yeejoin.amos.boot.module.ymt.api.mapper.TzBaseEnterpriseInfoMapper;
import com.yeejoin.amos.boot.module.ymt.flc.api.mapper.CylinderAreaDataMapper;
import com.yeejoin.amos.boot.module.ymt.flc.api.mapper.CylinderInfoMapper;
import com.yeejoin.amos.feign.privilege.Privilege;
import com.yeejoin.amos.feign.privilege.model.CompanyModel;
import com.yeejoin.amos.feign.systemctl.model.RegionModel;
import org.apache.commons.lang3.StringUtils;
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.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 气瓶区域统计表服务实现类
 *
 * @author system_generator
 * @date 2022-03-08
 */
@Service
public class CylinderDPStatisticsServiceImpl {

    private StCommonServiceImpl stCommonService;

    private RestHighLevelClient restHighLevelClient;

    private TzBaseEnterpriseInfoMapper baseEnterpriseInfoMapper;

    private CylinderAreaDataMapper cylinderAreaDataMapper;

    private CylinderStatisticsMapper cylinderStatisticsMapper;

    private CylinderInfoMapper cylinderInfoMapper;

    /**
     * 气瓶图列换算单位上限
     */
    private final static Long CYLINDER_LEGEND_UPPER_LIMIT = 100000L;

    public CylinderDPStatisticsServiceImpl(StCommonServiceImpl stCommonService, RestHighLevelClient restHighLevelClient, TzBaseEnterpriseInfoMapper baseEnterpriseInfoMapper, CylinderAreaDataMapper cylinderAreaDataMapper, CylinderStatisticsMapper cylinderStatisticsMapper, CylinderInfoMapper cylinderInfoMapper) {
        this.stCommonService = stCommonService;
        this.restHighLevelClient = restHighLevelClient;
        this.baseEnterpriseInfoMapper = baseEnterpriseInfoMapper;
        this.cylinderAreaDataMapper = cylinderAreaDataMapper;
        this.cylinderStatisticsMapper = cylinderStatisticsMapper;
        this.cylinderInfoMapper = cylinderInfoMapper;
    }


    public List<Map<String, Object>> getCylinderStatisticsData(String regionCode) throws IOException {
        // 构建搜索请求
        String orgCode = stCommonService.getAndSetOrgCode(regionCode);
        if (StringUtils.isEmpty(orgCode)) {
            return new ArrayList<>();
        }
        SearchRequest searchRequest = new SearchRequest("idx_biz_view_jg_all");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 构建查询条件
        searchSourceBuilder.query(
                QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("EQU_CATEGORY_CODE", "2300"))
                        .must(QueryBuilders.termQuery("EQU_STATE", EquimentEnum.ZAIYONG.getCode()))
                        // 按照管辖机构区域信息模糊查询
                        .must(QueryBuilders.wildcardQuery("ORG_BRANCH_CODE.keyword", QueryParser.escape(orgCode) + "*"))
        );
        TermsAggregationBuilder aggregation = AggregationBuilders.terms("USE_UNIT_CREDIT_CODE").field("USE_UNIT_CREDIT_CODE.keyword").size(300);
        searchSourceBuilder.aggregation(aggregation);
        searchRequest.source(searchSourceBuilder);
        // 执行搜索并获取响应
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 处理聚合结果
        // 这里只是打印出聚合的名称和桶的数量，你需要根据自己的需求来处理这些数据
        Terms terms = searchResponse.getAggregations().get("USE_UNIT_CREDIT_CODE");
        List<Map<String, Object>> dataList = new ArrayList<>();
        List<String> keys = new ArrayList<>();
        for (Terms.Bucket bucket : terms.getBuckets()) {
            Map<String, Object> item = new HashMap<>();
            item.put("count", bucket.getDocCount());
            item.put("key", bucket.getKeyAsString());
            dataList.add(item);
            keys.add(bucket.getKeyAsString());
        }
        Map<String, TzBaseEnterpriseInfo> useCodeEntryMap = getStringTzBaseEnterpriseInfoDtoMap(keys);
        for (Map<String, Object> item : dataList) {
            Object key = item.get("key");
            if (useCodeEntryMap.containsKey(key)) {
                item.put("name", useCodeEntryMap.get(key).getUseUnit());
                item.put("city", useCodeEntryMap.get(key).getCity());
                item.put("province", useCodeEntryMap.get(key).getProvince());
                item.put("district", useCodeEntryMap.get(key).getDistrict());
            }
        }
        return dataList;
    }

    private Map<String, TzBaseEnterpriseInfo> getStringTzBaseEnterpriseInfoDtoMap(List<String> keys) {
        LambdaQueryWrapper<TzBaseEnterpriseInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(TzBaseEnterpriseInfo::getUseUnitCode, keys);
        queryWrapper.select(TzBaseEnterpriseInfo::getUseUnitCode, TzBaseEnterpriseInfo::getProvince, TzBaseEnterpriseInfo::getCity, TzBaseEnterpriseInfo::getDistrict, TzBaseEnterpriseInfo::getUseUnit);
        List<TzBaseEnterpriseInfo> baseEnterpriseInfos = baseEnterpriseInfoMapper.selectList(queryWrapper);
        return baseEnterpriseInfos.stream().collect(Collectors.toMap(TzBaseEnterpriseInfo::getUseUnitCode, f -> f));
    }


    public Map<String, Object> getCylinderStatisticsDataByCity(DPFilterParamDto dpFilterParamDto) throws Exception {
        String regionCode = dpFilterParamDto.getCityCode();
        List<RegionModel> regionList = stCommonService.setRegionIfRootParent(regionCode);
        List<Map<String, Object>> legendData = new ArrayList<>();
        List<String> xdata = this.buildXData(regionList);
        List<Long> qiping = getYDataForQP(regionList);
        Long totalCyNum = qiping.stream().mapToLong(e -> e).sum();
        List<Long> qizhan = getYDataForQZ(regionList);
        Map<String, Object> qiLegend = getQPLegend(totalCyNum);
        Map<String, Object> zhanLegend = getQZLegend("气站数量", "qizhan");
        legendData.add(qiLegend);
        legendData.add(zhanLegend);
        Map<String, Object> result = new HashMap<>();
        result.put("qizhan", qizhan);
        result.put("qiping", this.changeNum2Million(totalCyNum, qiping));
        result.put("xdata", xdata);
        result.put("legendData", legendData);
        return result;
    }

    private List changeNum2Million(Long totalCyNum, List<Long> qiping) {
        if (totalCyNum > CYLINDER_LEGEND_UPPER_LIMIT) {
            return qiping.stream().map(num -> {
                BigDecimal numDecimal = new BigDecimal(num.toString());
                BigDecimal millionNum = numDecimal.divide(new BigDecimal("10000"), 4, RoundingMode.HALF_UP);
                return millionNum.toPlainString();
            }).collect(Collectors.toList());
        } else {
            return qiping;
        }
    }

    private Map<String, Object> getQPLegend(Long totalCyNum) {
        Map<String, Object> qiLegend = new HashMap<>();
        if (totalCyNum > CYLINDER_LEGEND_UPPER_LIMIT) {
            qiLegend.put("value", "气瓶数量(万)");
        } else {
            qiLegend.put("value", "气瓶数量");
        }
        qiLegend.put("dataKey", "qiping");
        return qiLegend;
    }

    private Map<String, Object> getQZLegend(String label, String key) {
        Map<String, Object> qiLegend = new HashMap<>();
        qiLegend.put("value", label);
        qiLegend.put("dataKey", key);
        return qiLegend;
    }

    private List<Long> getYDataForQZ(List<RegionModel> regionList) {
        List<CountDto> enterpriseCountList = regionList.parallelStream().map(e -> {
            CountDto dto = new CountDto();
            dto.setKeyStr(e.getRegionCode() + "");
            String orgCode = stCommonService.getAndSetOrgCode(e.getRegionCode() + "");
            if (StringUtils.isNotEmpty(orgCode)) {
                Long enterpriseNum = cylinderStatisticsMapper.countEnterpriseNumForCylinder(orgCode);
                dto.setLongValue(enterpriseNum);
            } else {
                dto.setLongValue(0L);
            }
            return dto;
        }).collect(Collectors.toList());
        return regionList.stream().map(r -> enterpriseCountList.stream().filter(e -> e.getKeyStr().equals(r.getRegionCode() + "")).mapToLong(CountDto::getLongValue).sum()).collect(Collectors.toList());
    }

    private List<Long> getYDataForQP(List<RegionModel> regionList) {
        List<Long> qiping = new ArrayList<>();
        regionList.forEach(r -> {
            String orgCode = stCommonService.getAndSetOrgCode(r.getRegionCode() + "");
            if (StringUtils.isNotEmpty(orgCode)) {
                Long num = this.countForCylinderNum(orgCode);
                qiping.add(num);
            } else {
                qiping.add(0L);
            }
        });
        return qiping;
    }

    private Long countForCylinderNum(String orgCode) {
        long num = 0;
        CountRequest request = new CountRequest();
        request.indices("idx_biz_view_jg_all");
        BoolQueryBuilder boolMust = QueryBuilders.boolQuery();
        boolMust.must(QueryBuilders.termQuery("EQU_CATEGORY_CODE", "2300"));
        // 按照管辖机构区域信息模糊查询
        boolMust.must(QueryBuilders.wildcardQuery("ORG_BRANCH_CODE.keyword", QueryParser.escape(orgCode) + "*"));
        // 且在用状态设备
        boolMust.must(QueryBuilders.termQuery("EQU_STATE", EquimentEnum.ZAIYONG.getCode()));
        request.query(boolMust);
        try {
            CountResponse response = restHighLevelClient.count(request, RequestOptions.DEFAULT);
            num = response.getCount();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return num;
    }

    private Long countForCylinderNumForVehicleUsed(String orgCode) {
        long num = 0;
        CountRequest request = new CountRequest();
        request.indices("idx_biz_view_jg_all");
        BoolQueryBuilder boolMust = QueryBuilders.boolQuery();
        // 按照管辖机构区域信息模糊查询
        boolMust.must(QueryBuilders.termQuery("WHETHER_VEHICLE_CYLINDER.keyword", "1"));
        boolMust.must(QueryBuilders.wildcardQuery("ORG_BRANCH_CODE.keyword", QueryParser.escape(orgCode) + "*"));
        // 且在用状态设备
        boolMust.must(QueryBuilders.termQuery("EQU_STATE", EquimentEnum.ZAIYONG.getCode()));
        request.query(boolMust);
        try {
            CountResponse response = restHighLevelClient.count(request, RequestOptions.DEFAULT);
            num = response.getCount();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return num;
    }

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

    public Map<String, Object> getCylinderStatisticsDataByCityForTotal(DPFilterParamDto dpFilterParamDto) {
        Map<String, Object> result = new HashMap<>();
        String orgCode = stCommonService.getAndSetOrgCode(dpFilterParamDto.getCityCode());
        getCylinderMapCount(orgCode, result);
        return result;
    }

    private Map<String, Object> getCylinderMapCount(String orgCode, Map<String, Object> result) {
        if (StringUtils.isNotEmpty(orgCode)) {
            result.put("cylindersCount", this.countForCylinderNum(orgCode));
            result.put("stationCount", cylinderStatisticsMapper.countEnterpriseNumForCylinder(orgCode));
            result.put("operatorCount", cylinderAreaDataMapper.getOpertorStatisticsDataByCity(Collections.singletonList(orgCode)));
            // 液化气瓶 (个)
            result.put("liquefiedGasCount", 0L);
            // 车用气瓶 (个)
            result.put("automotiveGasCount", this.countForCylinderNumForVehicleUsed(orgCode));
            // 工业气瓶 (个)
            result.put("industrialGasCount", 0L);
            // 使用登记数量
            result.put("useRegistrationQuantityCount", this.countForCylinderCertNum(orgCode));
            // 检验临期气瓶数
            result.put("jylqsbCount", this.countForCylinderTemporaryInspect(orgCode));
            // 检验超期气瓶数
            result.put("jycqsbCount", this.countForCylinderOverdueInspect(orgCode));
        } else {
            this.setDefaultValueIfNoData(result, "cylindersCount", "stationCount", "operatorCount", "liquefiedGasCount", "automotiveGasCount", "industrialGasCount", "useRegistrationQuantityCount", "jylqsbCount", "jycqsbCount");
        }
        return result;
    }

    /**
     * 临期检验设备数统计
     *
     * @param orgCode 过滤条件
     * @return 临期设备的数量
     */
    private Long countForCylinderTemporaryInspect(String orgCode) {
        long num = 0;
        CountRequest request = new CountRequest();
        request.indices("idx_biz_view_jg_all");
        BoolQueryBuilder boolMust = QueryBuilders.boolQuery();
        boolMust.must(QueryBuilders.termQuery("EQU_CATEGORY_CODE", "2300"));
        // 按照管辖机构区域信息模糊查询
        boolMust.must(QueryBuilders.wildcardQuery("ORG_BRANCH_CODE.keyword", QueryParser.escape(orgCode) + "*"));
        // 且在用状态设备
        boolMust.must(QueryBuilders.termQuery("EQU_STATE", EquimentEnum.ZAIYONG.getCode()));
        // 且下次检验日期大于等于当天
        long currentDayTime = DateUtil.parse(DateUtil.today(), "yyy-MM-dd").getTime();
        // 且查询 下次检验日期 <= 当前天+30天 极为临期
        long currentDayAfter30DayTime = DateUtil.offsetDay(DateUtil.parse(DateUtil.today(), "yyy-MM-dd"), 30).getTime();
        boolMust.must(QueryBuilders.rangeQuery("NEXT_INSPECT_DATE").gte(currentDayTime).lte(currentDayAfter30DayTime));
        request.query(boolMust);
        try {
            CountResponse response = restHighLevelClient.count(request, RequestOptions.DEFAULT);
            num = response.getCount();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return num;
    }


    /**
     * 超期检验设备数统计
     *
     * @param orgCode 过滤条件
     * @return 超期设备的数量
     */
    private Long countForCylinderOverdueInspect(String orgCode) {
        long num = 0;
        CountRequest request = new CountRequest();
        request.indices("idx_biz_view_jg_all");
        BoolQueryBuilder boolMust = QueryBuilders.boolQuery();
        // 设备类别2300气瓶
        boolMust.must(QueryBuilders.termQuery("EQU_CATEGORY_CODE", "2300"));
        // 按照管辖机构区域信息模糊查询
        boolMust.must(QueryBuilders.wildcardQuery("ORG_BRANCH_CODE.keyword", QueryParser.escape(orgCode) + "*"));
        // 在用状态设备
        boolMust.must(QueryBuilders.termQuery("EQU_STATE", EquimentEnum.ZAIYONG.getCode()));
        // 查询下次检验日期小于当前天的设备，即为超期检验超期设备
        long currentDayTime = DateUtil.parse(DateUtil.now(), "yyy-MM-dd").getTime();
        boolMust.must(QueryBuilders.rangeQuery("NEXT_INSPECT_DATE").lt(currentDayTime));
        request.query(boolMust);
        try {
            CountResponse response = restHighLevelClient.count(request, RequestOptions.DEFAULT);
            num = response.getCount();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return num;
    }

    private Long countForCylinderCertNum(String orgCode) {
        return cylinderStatisticsMapper.countForCylinderCertNum(orgCode);
    }

    public List<Map<String, Object>> getCylinderStatisticsDataByCityForMap(DPFilterParamDto dpFilterParamDto) throws Exception {
        List<RegionModel> regionList = stCommonService.setRegionIfRootParent(dpFilterParamDto.getCityCode());
        return regionList.parallelStream().map(r -> {
            String orgCode = stCommonService.getAndSetOrgCode(r.getRegionCode().toString());
            Map<String, Object> item = new HashMap<>();
            item.put("regionCode", r.getRegionCode());
            item.put("regionName", r.getRegionName());
            getCylinderMapCount(orgCode, item);
            return item;
        }).collect(Collectors.toList());
    }

    private void setDefaultValueIfNoData(Map<String, Object> item, String... fields) {
        for (String field : fields) {
            item.put(field, 0L);
        }
    }

    public List<CylinderFillingRecordStatisticsDto> fillingTimesAndQuantity(String reginCode) {
        String orgCode = null;
        CompanyModel result = Privilege.companyClient.queryByCompanyCode(reginCode).getResult();
        orgCode = result.getOrgCode();

        List<String> times = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            times.add(LocalDate.now().minusDays(i).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        }
        List<CylinderFillingRecordStatisticsDto> fillingRecord = cylinderInfoMapper.queryFillingRecordByOrgCode(orgCode, LocalDate.now().minusDays(29).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        List<CylinderFillingRecordStatisticsDto> offloading = cylinderInfoMapper.queryoffloadingByOrgCode(orgCode, LocalDate.now().minusDays(29).format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        if (fillingRecord == null) {
            fillingRecord = new ArrayList<>(0);
        }
        if (offloading == null) {
            offloading = new ArrayList<>(0);
        }
        Map<String, String> fillingRecordMap = fillingRecord.stream().collect(Collectors.toMap(CylinderFillingRecordStatisticsDto::getTime, f -> f.getFillingQuantity()));
        Map<String, String> offloadingMap = offloading.stream().collect(Collectors.toMap(CylinderFillingRecordStatisticsDto::getTime, f -> f.getOffloadingVolume()));
        List<CylinderFillingRecordStatisticsDto> data = new ArrayList<>();
        for (String key : times) {
            CylinderFillingRecordStatisticsDto dayData = new CylinderFillingRecordStatisticsDto();
            dayData.setTime(key);
            dayData.setFillingQuantity(fillingRecordMap.getOrDefault(key, "0"));
            dayData.setOffloadingVolume(offloadingMap.getOrDefault(key, "0"));
            data.add(dayData);
        }
        return data;
    }


}