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

import cn.hutool.core.date.DateUtil;
import com.yeejoin.amos.boot.biz.common.dto.CountDto;
import com.yeejoin.amos.boot.module.common.api.dto.DPFilterParamDto;
import com.yeejoin.amos.boot.module.common.api.dto.LegendDataDto;
import com.yeejoin.amos.boot.module.jg.api.dto.EquipBizCountDto;
import com.yeejoin.amos.boot.module.jg.api.enums.DPMapStatisticsItemEnum;
import com.yeejoin.amos.boot.module.jg.api.mapper.CommonMapper;
import com.yeejoin.amos.boot.module.jg.api.mapper.JgEnableDisableMapper;
import com.yeejoin.amos.boot.module.jg.api.mapper.JgScrapCancelMapper;
import com.yeejoin.amos.boot.module.jg.api.mapper.JgUseRegistrationMapper;
import com.yeejoin.amos.boot.module.ymt.api.dto.EquipmentCategoryDto;
import com.yeejoin.amos.boot.module.ymt.api.mapper.EquipTechParamPipelineMapper;
import com.yeejoin.amos.boot.module.ymt.api.mapper.EquipmentCategoryMapper;
import com.yeejoin.amos.boot.module.ymt.api.mapper.TzBaseEnterpriseInfoMapper;
import com.yeejoin.amos.boot.module.ymt.api.mapper.TzsUserInfoMapper;
import com.yeejoin.amos.feign.systemctl.Systemctl;
import com.yeejoin.amos.feign.systemctl.model.RegionModel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * 大屏统计实现类
 *
 * @author Administrator
 */
@Service
@Slf4j
public class DPStatisticsServiceImpl {

    /**
     * 压力容器设备种类
     */
    private final static String EQU_LIST_CYLINDER = "2000";

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

    /**
     * 单位类型-使用单位
     */
    private final static String COMPANY_TYPE_USE = "使用单位";

    /**
     * 单位类型-安装改造维修单位
     */
    private final static String COMPANY_TYPE_MAINTENANCE = "安装改造维修单位";

    /**
     * 单位类型-制造单位
     */
    private final static String COMPANY_TYPE_MANUFACTURE = "制造单位";

    /**
     * 单位类型-充装单位
     */
    private final static String COMPANY_TYPE_FILLING = "充装单位";


    private EquipmentCategoryMapper equipmentCategoryMapper;

    private JgUseRegistrationMapper useRegistrationMapper;

    private EquipTechParamPipelineMapper techParamsPipelineMapper;

    private JgEnableDisableMapper enableDisableMapper;

    private JgScrapCancelMapper scrapCancelMapper;

    private RestHighLevelClient restHighLevelClient;

    private TzBaseEnterpriseInfoMapper enterpriseInfoMapper;

    private CommonMapper commonMapper;

    private static List<EquipmentCategoryDto> equipmentCategoryDtos;

    private TzsUserInfoMapper userInfoMapper;

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

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


    public DPStatisticsServiceImpl(EquipmentCategoryMapper equipmentCategoryMapper, JgUseRegistrationMapper useRegistrationMapper, EquipTechParamPipelineMapper techParamsPipelineMapper, JgEnableDisableMapper enableDisableMapper, JgScrapCancelMapper scrapCancelMapper, RestHighLevelClient restHighLevelClient, TzBaseEnterpriseInfoMapper enterpriseInfoMapper, CommonMapper commonMapper, TzsUserInfoMapper userInfoMapper) {
        this.equipmentCategoryMapper = equipmentCategoryMapper;
        this.useRegistrationMapper = useRegistrationMapper;
        this.techParamsPipelineMapper = techParamsPipelineMapper;
        this.enableDisableMapper = enableDisableMapper;
        this.scrapCancelMapper = scrapCancelMapper;
        this.restHighLevelClient = restHighLevelClient;
        this.enterpriseInfoMapper = enterpriseInfoMapper;
        this.commonMapper = commonMapper;
        this.userInfoMapper = userInfoMapper;
    }

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


    private List<LegendDataDto> buildLegendDataList() {
        List<LegendDataDto> legendDataDtos = new ArrayList<>();
        legendDataDtos.add(LegendDataDto.builder().dataKey("newDevice").value("新增登记设备").build());
        legendDataDtos.add(LegendDataDto.builder().dataKey("stoppedDevice").value("报停设备").build());
        legendDataDtos.add(LegendDataDto.builder().dataKey("scrappedDevice").value("报废设备").build());
        return legendDataDtos;
    }

    public Map<String, Object> useRegisterCountByEquList(DPFilterParamDto dpFilterParamDto) {
        // 1.查询条件构造未上送时间时，默认查询数据为近一个月数据
        this.setDefaultFilter(dpFilterParamDto);
        // 2.按照前端约定格式返回数据
        Map<String, Object> result = new HashMap<>();
        List<LegendDataDto> legendDataDtos = this.buildLegendDataList();
        //2.1 图列数据构造
        result.put("legendData", legendDataDtos);
        //2.2 x轴数据构造
        result.put("xdata", this.getXData(equipmentCategoryDtos));
        //2.3 y轴数据构造
        this.buildYData(result, dpFilterParamDto, equipmentCategoryDtos);
        return result;
    }

    private void buildYData(Map<String, Object> result, DPFilterParamDto dpFilterParamDto, List<EquipmentCategoryDto> equipmentCategoryDtos) {
        // 待统计的数据本来sql可以单独统计，但是要求气瓶单独从压力容器里拎出来，与八大类同级，故java处理
        // 1.新增登记统计
        result.put("newDevice", this.getNewDeviceCountData(dpFilterParamDto, equipmentCategoryDtos));
        // 2.新增报停设备统计
        result.put("stoppedDevice", this.getStoppedDeviceCountData(dpFilterParamDto, equipmentCategoryDtos));
        // 3.新增报废设备统计
        result.put("scrappedDevice", this.getScrappedDeviceCountData(dpFilterParamDto, equipmentCategoryDtos));
    }

    private List<Integer> getNewDeviceCountData(DPFilterParamDto dpFilterParamDto, List<EquipmentCategoryDto> equipmentCategoryDtos) {
        List<EquipBizCountDto> staticsData = useRegistrationMapper.queryNewDeviceStaticListData(dpFilterParamDto);
        return countNumByEquList(equipmentCategoryDtos, staticsData);
    }

    private List<Integer> getStoppedDeviceCountData(DPFilterParamDto dpFilterParamDto, List<EquipmentCategoryDto> equipmentCategoryDtos) {
        List<EquipBizCountDto> staticsData = enableDisableMapper.queryStoppedDeviceStaticListData(dpFilterParamDto);
        return countNumByEquList(equipmentCategoryDtos, staticsData);
    }

    private List<Integer> getScrappedDeviceCountData(DPFilterParamDto dpFilterParamDto, List<EquipmentCategoryDto> equipmentCategoryDtos) {
        List<EquipBizCountDto> staticsData = scrapCancelMapper.queryScrappedDeviceStaticListData(dpFilterParamDto);
        return countNumByEquList(equipmentCategoryDtos, staticsData);
    }


    private List<Integer> countNumByEquList(List<EquipmentCategoryDto> equipmentCategoryDtos, List<EquipBizCountDto> bizCountDtos) {
        List<Integer> lineData = new ArrayList<>();
        // 8大类数据统计数量
        equipmentCategoryDtos.forEach(equipmentCategoryDto -> {
            int num = 0;
            // 压力容器要去掉气瓶2300，气瓶单独统计
            if (EQU_LIST_CYLINDER.equals(equipmentCategoryDto.getCode())) {
                num = bizCountDtos.stream().filter(c -> c.getEquList().equals(equipmentCategoryDto.getCode()) && !EQU_CATEGORY_CYLINDER.equals(c.getEquCategory())).mapToInt(EquipBizCountDto::getNum).sum();
            } else {
                num = bizCountDtos.stream().filter(c -> c.getEquList().equals(equipmentCategoryDto.getCode())).mapToInt(EquipBizCountDto::getNum).sum();
            }
            lineData.add(num);
        });
        // 再增加气瓶(设备类别2300)专项
        int num = bizCountDtos.stream().filter(c -> EQU_CATEGORY_CYLINDER.equals(c.getEquCategory())).mapToInt(EquipBizCountDto::getNum).sum();
        lineData.add(num);
        return lineData;
    }

    private List<String> getXData(List<EquipmentCategoryDto> equipmentCategoryDtos) {
        // 8大类  + 气瓶 注意数据顺序和要和这个x轴一致
        List<String> names = equipmentCategoryDtos.stream().map(EquipmentCategoryDto::getName).collect(Collectors.toList());
        names.add("气瓶");
        return names;
    }


    private void setDefaultFilter(DPFilterParamDto dpFilterParamDto) {
        if (StringUtils.isEmpty(dpFilterParamDto.getBeginDate())) {
            dpFilterParamDto.setBeginDate(DateUtil.lastMonth().toDateStr());
        }
        if (StringUtils.isEmpty(dpFilterParamDto.getEndDate())) {
            dpFilterParamDto.setEndDate(DateUtil.today());
        }
    }


    public Map<String, Object> getCenterMapCountDataForGlobal(DPFilterParamDto dpFilterParamDto) {
        Map<String, Object> result = new HashMap<>();
        // 1.气瓶数量统计
        long cylinderNum = this.staticsCenterMapCountDataForCylinder(result, dpFilterParamDto);
        //1.8大类设备数量统计,压力容器里包括气瓶所以需要特殊处理，在统计压力容器时去掉气瓶的数量
        this.staticsCenterMapCountDataForEquip(result, dpFilterParamDto, cylinderNum);
        //2.压力管道长度统计
        this.staticsCenterMapCountDataForPipeline(result, dpFilterParamDto);
        //3.单位数量统计
        this.staticsCenterMapCountDataForCompany(result, dpFilterParamDto);
        //4.人员数量统计
        this.staticsCenterMapCountDataForPerson(result, dpFilterParamDto);
        return result;
    }

    public Map<String, Object> getCenterMapOverviewData(DPFilterParamDto dpFilterParamDto) {
        Map<String, Object> result = new HashMap<>();
        // 1.气瓶数量统计
        long cylinderNum = this.staticsCenterMapCountDataForCylinder(result, dpFilterParamDto);
        //1.8大类设备数量统计,压力容器里包括气瓶所以需要特殊处理，在统计压力容器时去掉气瓶的数量
        this.staticsCenterMapCountDataForEquip(result, dpFilterParamDto, cylinderNum);
        //2.压力管道长度统计
        this.staticsCenterMapCountDataForPipeline(result, dpFilterParamDto);
        //3.人员数量统计
        this.staticsCenterMapCountDataForPerson(result, dpFilterParamDto);
        return result;
    }

    private void staticsCenterMapCountDataForPerson(Map<String, Object> result, DPFilterParamDto dpFilterParamDto) {
        // 效率太慢暂时注释掉
        String orgCode = regionCodeOrgCodeMap.get(dpFilterParamDto.getCityCode());
        if (orgCode == null) {
            orgCode = commonMapper.getOrgCodeByCompanyCode(dpFilterParamDto.getCityCode());
            if (orgCode == null) {
                result.put(DPMapStatisticsItemEnum.OPERATORS.getCode(), 0);
                return;
            }
            regionCodeOrgCodeMap.put(dpFilterParamDto.getCityCode(), orgCode);
        }
        Long num = userInfoMapper.countUserByPostAndAreaCode(orgCode, "6552");
        result.put(DPMapStatisticsItemEnum.OPERATORS.getCode(), num);
    }

    private void staticsCenterMapCountDataForCompany(Map<String, Object> result, DPFilterParamDto dpFilterParamDto) {
        String orgCode = regionCodeOrgCodeMap.get(dpFilterParamDto.getCityCode());
        if (orgCode == null) {
            orgCode = commonMapper.getOrgCodeByCompanyCode(dpFilterParamDto.getCityCode());
            if (orgCode == null) {
                setDefaultCompanyCountData(result);
                return;
            }
            regionCodeOrgCodeMap.put(dpFilterParamDto.getCityCode(), orgCode);
        }
        List<CountDto> countDtos = enterpriseInfoMapper.countByUnitTypeAndOrgCode(orgCode);
        result.put(DPMapStatisticsItemEnum.USERS_UNITS.getCode(), countDtos.stream().filter(c -> c.getKeyStr().contains(COMPANY_TYPE_USE)).mapToInt(CountDto::getIntValue).sum());
        result.put(DPMapStatisticsItemEnum.CONSTRUCTION_UNITS.getCode(), countDtos.stream().filter(c -> c.getKeyStr().contains(COMPANY_TYPE_MAINTENANCE)).mapToInt(CountDto::getIntValue).sum());
        result.put(DPMapStatisticsItemEnum.MANUFACTURING_UNITS.getCode(), countDtos.stream().filter(c -> c.getKeyStr().contains(COMPANY_TYPE_MANUFACTURE)).mapToInt(CountDto::getIntValue).sum());
        result.put(DPMapStatisticsItemEnum.GAS_UNITS.getCode(), countDtos.stream().filter(c -> c.getKeyStr().contains(COMPANY_TYPE_FILLING)).mapToInt(CountDto::getIntValue).sum());
    }

    private long staticsCenterMapCountDataForCylinder(Map<String, Object> result, DPFilterParamDto dpFilterParamDto) {
        long num = 0;
        CountRequest request = new CountRequest();
        request.indices("idx_biz_view_jg_all");
        BoolQueryBuilder boolMust = QueryBuilders.boolQuery();
        // 区域信息模糊查询
        boolMust.must(QueryBuilders.wildcardQuery("USE_PLACE_CODE", "*" + dpFilterParamDto.getCityCode() + "*"));
        // 设备类别精确查询气瓶
        boolMust.must(QueryBuilders.termsQuery("EQU_CATEGORY_CODE", EQU_CATEGORY_CYLINDER));
        // 纳管状态为已纳管
        boolMust.must(QueryBuilders.termsQuery("IS_INTO_MANAGEMENT", true));
        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;
    }

    private void staticsCenterMapCountDataForPipeline(Map<String, Object> result, DPFilterParamDto dpFilterParamDto) {
        String length = techParamsPipelineMapper.sumPipeLengthByArea(dpFilterParamDto.getCityCode());
        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);
    }


    private void staticsCenterMapCountDataForEquip(Map<String, Object> result, DPFilterParamDto dpFilterParamDto, long cylinderNum) {
        SearchRequest request = new SearchRequest();
        request.indices("idx_biz_view_jg_all");
        BoolQueryBuilder boolMust = QueryBuilders.boolQuery();
        // 区域信息模糊查询
        boolMust.must(QueryBuilders.wildcardQuery("USE_PLACE_CODE", "*" + dpFilterParamDto.getCityCode() + "*"));
        // 纳管状态为已纳管
        boolMust.must(QueryBuilders.termsQuery("IS_INTO_MANAGEMENT", true));
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(boolMust);
        TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("count_by_equ_list_code").field("EQU_LIST_CODE");
        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()) {
                // 压力容器里包括气瓶所以需要特殊处理，在统计压力容器时去掉气瓶的数量
                if (bucket.getKeyAsString().equals(EQU_LIST_CYLINDER)) {
                    countMap.put(bucket.getKeyAsString(), bucket.getDocCount() - cylinderNum);
                } else {
                    countMap.put(bucket.getKeyAsString(), bucket.getDocCount());
                }
            }
            // 按照8大类枚举，进行加工。目的：固定八大类防止没统计数据导致缺少分类、将设备种类的code换成前端定义的key
            equipmentCategoryDtos.forEach(c -> {
                result.put(this.castCategoryCode2WebCode(c.getCode()), countMap.getOrDefault(c.getCode(), 0L));
            });
            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 Map<String, Object> useRegisterCount(DPFilterParamDto dpFilterParamDto) {
        // 2.按照前端约定格式返回数据
        Map<String, Object> result = new HashMap<>();

        List legendDataList = new ArrayList();
        Map<String, Object> newDeviceMap = new HashMap<>();
        newDeviceMap.put("dataKey", "newDevice");
        newDeviceMap.put("value", "新增登记设备");
        legendDataList.add(newDeviceMap);
        Map<String, Object> scrappedDeviceMap = new HashMap<>();
        scrappedDeviceMap.put("dataKey", "scrappedDevice");
        scrappedDeviceMap.put("value", "报废设备");
        legendDataList.add(scrappedDeviceMap);
        result.put("legendData", legendDataList);
        LocalDate today = LocalDate.now();
        List xDataList = new ArrayList();
        List<Integer> newDeviceList = new ArrayList();
        List<Integer> scrappedDeviceList = new ArrayList();
        DateTimeFormatter sdf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        for (int i = 2; i >= 0; i--) {
            LocalDate firstDayOfPrevMonth = today.minusMonths(i).with(TemporalAdjusters.firstDayOfMonth());
            LocalDate lastDayOfPrevMonth = today.minusMonths(i).with(TemporalAdjusters.lastDayOfMonth());
            dpFilterParamDto.setBeginDate(LocalDateTime.of(firstDayOfPrevMonth, java.time.LocalTime.MIN).format(sdf));
            dpFilterParamDto.setEndDate(LocalDateTime.of(lastDayOfPrevMonth, java.time.LocalTime.MAX).format(sdf));
            Integer useRegisterCount = useRegistrationMapper.getUseRegisterCount(dpFilterParamDto);
            Integer scrappedDeviceCount = scrapCancelMapper.getScrappedDeviceCount(dpFilterParamDto);
            xDataList.add(firstDayOfPrevMonth.getMonthValue() + "月");
            newDeviceList.add(null == useRegisterCount ? 0 : useRegisterCount);
            scrappedDeviceList.add(null == scrappedDeviceCount ? 0 : scrappedDeviceCount);
        }
        result.put("xdata", xDataList);
        result.put("newDevice", newDeviceList);
        result.put("scrappedDevice", scrappedDeviceList);
        return result;

    }

    public List<Map<String, Object>> getCenterMapCountDataForOverview(DPFilterParamDto dpFilterParamDto) {
        List<Map<String, Object>> result = regionModels.parallelStream().filter(e -> e.getParentRegionCode() != null && (e.getParentRegionCode().toString()).equals(dpFilterParamDto.getCityCode())).map(r -> {
            DPFilterParamDto filterParamDto = new DPFilterParamDto();
            filterParamDto.setCityCode(r.getRegionCode().toString());
            Map<String, Object> itemResult = getCenterMapOverviewData(filterParamDto);
            if (itemResult == null) {
                itemResult = new HashMap<>();
            }
            itemResult.put("cityCode", r.getRegionCode());
            return itemResult;
        }).collect(Collectors.toList());
        this.setCompanyDataBatch(result);
        return result;
    }

    private void setCompanyDataBatch(List<Map<String, Object>> result) {

        List<CountDto> countDtos = enterpriseInfoMapper.countByUnitTypeAndOrgCodeNoParam();
        result.forEach(m -> {
            String cityCode = m.get("cityCode").toString();
            String orgCode = regionCodeOrgCodeMap.get(cityCode);
            if (orgCode != null) {
                m.put(DPMapStatisticsItemEnum.USERS_UNITS.getCode(), countDtos.stream().filter(c -> c.getKeyStr().contains(COMPANY_TYPE_USE) && c.getLabel().contains(orgCode)).mapToInt(CountDto::getIntValue).sum());
                m.put(DPMapStatisticsItemEnum.CONSTRUCTION_UNITS.getCode(), countDtos.stream().filter(c -> c.getKeyStr().contains(COMPANY_TYPE_MAINTENANCE) && c.getLabel().contains(orgCode)).mapToInt(CountDto::getIntValue).sum());
                m.put(DPMapStatisticsItemEnum.MANUFACTURING_UNITS.getCode(), countDtos.stream().filter(c -> c.getKeyStr().contains(COMPANY_TYPE_MANUFACTURE) && c.getLabel().contains(orgCode)).mapToInt(CountDto::getIntValue).sum());
                m.put(DPMapStatisticsItemEnum.GAS_UNITS.getCode(), countDtos.stream().filter(c -> c.getKeyStr().contains(COMPANY_TYPE_FILLING) && c.getLabel().contains(orgCode)).mapToInt(CountDto::getIntValue).sum());
            } else {
                setDefaultCompanyCountData(m);
            }
        });
    }

    private void setDefaultCompanyCountData(Map<String, Object> m) {
        m.put(DPMapStatisticsItemEnum.USERS_UNITS.getCode(), 0);
        m.put(DPMapStatisticsItemEnum.CONSTRUCTION_UNITS.getCode(), 0);
        m.put(DPMapStatisticsItemEnum.MANUFACTURING_UNITS.getCode(), 0);
        m.put(DPMapStatisticsItemEnum.GAS_UNITS.getCode(), 0);
    }

    private void initReginCode() {
        Collection<RegionModel> result = Systemctl.regionClient.queryForTree(null).getResult();
        result.forEach(r -> {
            regionModels.add(r);
            this.loopSetChildRegin(regionModels, r.getChildren());
        });
    }

    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());
            });
        }
    }
}
