/*
 * 文 件 名:  ChargingBillServiceImpl
 * 版    权:  浩鲸云计算科技股份有限公司
 * 描    述:  <描述>
 * 修 改 人:  lvwenbin
 * 修改时间:  2024/9/2
 * 跟踪单号:  <跟踪单号>
 * 修改单号:  <修改单号>
 * 修改内容:  <修改内容>
 */
package com.ruoyi.ems.service.impl;

import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.ems.domain.Area;
import com.ruoyi.ems.domain.AreaBuilding;
import com.ruoyi.ems.domain.AreaBuildingZoning;
import com.ruoyi.ems.domain.ChargingBill;
import com.ruoyi.ems.domain.CoChargingConfig;
import com.ruoyi.ems.domain.CoChargingConfigRel;
import com.ruoyi.ems.domain.MeterDevice;
import com.ruoyi.ems.domain.MeterReading;
import com.ruoyi.ems.domain.common.AreaTier;
import com.ruoyi.ems.domain.common.MeterObjSubType;
import com.ruoyi.ems.domain.common.MeterObjTag;
import com.ruoyi.ems.domain.common.MeterObjType;
import com.ruoyi.ems.domain.vo.QueryChargingBill;
import com.ruoyi.ems.domain.vo.QueryMeterReading;
import com.ruoyi.ems.mapper.MeterDeviceMapper;
import com.ruoyi.ems.mapper.MeterReadingMapper;
import com.ruoyi.ems.service.IAreaBuildingService;
import com.ruoyi.ems.service.IAreaBuildingZoningService;
import com.ruoyi.ems.service.IAreaService;
import com.ruoyi.ems.service.IChargingBillService;
import com.ruoyi.ems.service.ICoChargingConfigService;
import com.ruoyi.ems.util.DateUtils;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 账单服务接口
 * <功能详细描述>
 *
 * @author lvwenbin
 * @version [版本号, 2024/9/2]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Service
public class ChargingBillServiceImpl implements IChargingBillService {

    @Autowired
    private MeterReadingMapper meterReadingMapper;

    @Autowired
    private MeterDeviceMapper meterDeviceMapper;

    @Autowired
    private IAreaService areaService;

    @Autowired
    private IAreaBuildingService buildingService;

    @Autowired
    private IAreaBuildingZoningService zoningService;

    @Autowired
    private ICoChargingConfigService coChargingConfigService;

    /**
     * 查询商户计费账单列表
     *
     * @param queryParam 查询参数
     * @return 返回列表
     */
    @Override
    public List<ChargingBill> selectCoChargingBill(QueryChargingBill queryParam) {
        // 查询园区内部计费配置
        CoChargingConfig chargingCfg = coChargingConfigService.selectByArea(queryParam.getAreaCode());

        // 计算个户账单
        Map<String, ChargingBill> coBillMap = clacCoBill(queryParam, chargingCfg);

        // 计算公摊账单
        Map<String, ChargingBill> sharedMap = clacSharedBill(queryParam, chargingCfg);

        return mergeBill(queryParam, coBillMap, sharedMap);
    }

    @Override
    public List<ChargingBill> selectTagChargingBill(QueryChargingBill queryParam) {
        // 查询园区内部计费配置
        CoChargingConfig chargingCfg = coChargingConfigService.selectByArea(queryParam.getAreaCode());

        Map<String, Long> individualMap = getQuantityByTag(queryParam, MeterObjTag.INDIVIDUAL);
        Map<String, Long> sharedMap = getQuantityByTag(queryParam, MeterObjTag.SHARED);

        List<ChargingBill> retList = new ArrayList<>();
        Date start = DateUtils.stringToDate(queryParam.getStartTime(), "yyyyMM");
        Date end = DateUtils.stringToDate(queryParam.getEndTime(), "yyyyMM");
        String objName = getObjName(queryParam);

        do {
            String time = DateUtils.dateToString(start, "yyyyMM");

            ChargingBill bill = new ChargingBill();
            bill.setObjCode(queryParam.getObjArea());
            bill.setMeteredTime(time);
            bill.setObjName(objName);

            long individual = (null != individualMap.get(time)) ? individualMap.get(time) : 0L;
            bill.setMeteredValue((double) individual);
            bill.setMeteredPrice(calcPrice(queryParam.getMeterCls(), individual, chargingCfg));

            long shared = (null != sharedMap.get(time)) ? sharedMap.get(time) : 0L;
            bill.setSharedValue(shared);
            bill.setSharedPrice(calcPrice(queryParam.getMeterCls(), shared, chargingCfg));

            bill.setTotalValue(individual + shared);
            bill.setTotalPrice(clacTotalPrice(bill));

            retList.add(bill);

            start = DateUtils.adjustMonth(start, 1);
        }
        while (start.before(end) || start.equals(end));

        return retList;
    }

    private List<ChargingBill> mergeBill(QueryChargingBill queryParam, Map<String, ChargingBill> coBillMap,
        Map<String, ChargingBill> sharedMap) {
        List<ChargingBill> retList = new ArrayList<>();
        Date start = DateUtils.stringToDate(queryParam.getStartTime(), "yyyyMM");
        Date end = DateUtils.stringToDate(queryParam.getEndTime(), "yyyyMM");
        String objName = getObjName(queryParam);

        do {
            String time = DateUtils.dateToString(start, "yyyyMM");

            ChargingBill bill = new ChargingBill();
            bill.setObjCode(queryParam.getObjArea());
            bill.setMeteredTime(time);
            bill.setObjName(objName);

            ChargingBill coBill = coBillMap.get(time);

            if (null != coBill) {
                bill.setMeteredValue(coBill.getMeteredValue());
                bill.setMeteredPrice(coBill.getMeteredPrice());
            }

            ChargingBill sharedBill = sharedMap.get(time);

            if (null != sharedBill) {
                bill.setSharedPrice(sharedBill.getSharedPrice());
                bill.setSharedComputeType(sharedBill.getSharedComputeType());
            }

            bill.setTotalPrice(clacTotalPrice(bill));

            retList.add(bill);

            start = DateUtils.adjustMonth(start, 1);
        }
        while (start.before(end) || start.equals(end));

        return retList;
    }

    private Map<String, ChargingBill> clacCoBill(QueryChargingBill queryParam, CoChargingConfig chargingCfg) {
        Map<String, ChargingBill> retMap = null;

        // 查询计量设备
        int objType = MeterObjType.METER_ABZ.getType();
        int objSubType = MeterObjSubType.getByTier(queryParam.getTier()).getType();

        MeterDevice meterDevice = meterDeviceMapper.selectMeterDeviceByObj(objType, objSubType, queryParam.getObjArea(),
            queryParam.getMeterCls());

        if (null != meterDevice) {
            QueryMeterReading recParam = new QueryMeterReading();
            recParam.setAreaCode(meterDevice.getAreaCode());
            recParam.setDeviceCode(meterDevice.getDeviceCode());
            recParam.setStartMonth(queryParam.getStartTime());
            recParam.setEndMonth(queryParam.getEndTime());

            List<MeterReading> recList = meterReadingMapper.selectList(recParam);
            List<ChargingBill> billList = convertToChargingBill(queryParam, chargingCfg, recList);
            retMap = billList.stream().collect(Collectors.toMap(ChargingBill::getMeteredTime, Function.identity()));
        }

        return MapUtils.isNotEmpty(retMap) ? retMap : new HashMap<>();
    }

    private Map<String, ChargingBill> clacSharedBill(QueryChargingBill queryParam, CoChargingConfig chargingCfg) {
        Map<String, ChargingBill> retMap = null;

        if (queryParam.getMeterCls() == 45) {
            retMap = clacElecSharedPrice(queryParam, chargingCfg);
        }
        else if (queryParam.getMeterCls() == 70) {
            retMap = clacWaterSharedPrice(queryParam, chargingCfg);
        }

        return MapUtils.isNotEmpty(retMap) ? retMap : new HashMap<>();
    }

    private List<ChargingBill> convertToChargingBill(QueryChargingBill queryParam, CoChargingConfig chargingCfg,
        List<MeterReading> recList) {
        List<ChargingBill> retList = new ArrayList<>();

        for (MeterReading rec : recList) {
            ChargingBill bill = new ChargingBill();
            bill.setMeteredValue(rec.getIncrease().doubleValue());
            bill.setMeteredTime(rec.getMeterMonth());

            // 计算商户个体费用
            if (null != chargingCfg) {
                if (queryParam.getMeterCls() == 45) {
                    bill.setMeteredPrice(calcPrice(rec.getIncrease(), chargingCfg.getElecUnitPrice()));
                }
                else if (queryParam.getMeterCls() == 70) {
                    bill.setMeteredPrice(calcPrice(rec.getIncrease(), chargingCfg.getWaterUnitPrice()));
                }
            }

            retList.add(bill);
        }

        return retList;
    }

    private Map<String, ChargingBill> clacElecSharedPrice(QueryChargingBill queryParam, CoChargingConfig chargingCfg) {
        Map<String, ChargingBill> retMap = new HashMap<>();
        // 参与公摊的分区ID
        Set<String> coSet = getCoSet(chargingCfg);

        // 电价公摊计算
        if (chargingCfg.getElecGtComputeType() > 0 && coSet.contains(queryParam.getObjArea())) {
            QueryMeterReading param = new QueryMeterReading(queryParam.getAreaCode(), MeterObjTag.SHARED.getTagValue(),
                queryParam.getMeterCls(), queryParam.getStartTime(), queryParam.getEndTime());
            List<MeterReading> totalReadings = meterReadingMapper.selectByTag(param);
            Map<String, List<MeterReading>> groupedRecs = totalReadings.stream()
                .collect(Collectors.groupingBy(MeterReading::getMeterMonth));

            for (Map.Entry<String, List<MeterReading>> entry : groupedRecs.entrySet()) {
                String meterTime = entry.getKey();
                List<MeterReading> sharedReadings = entry.getValue();

                // 公摊价格计算
                Double price = clacSharedPrice(sharedReadings, coSet, queryParam.getObjArea(),
                    chargingCfg.getElecGtComputeType(), chargingCfg.getElecUnitPrice());

                ChargingBill bill = new ChargingBill();
                bill.setSharedPrice(price);
                bill.setSharedComputeType(chargingCfg.getElecGtComputeType());

                retMap.put(meterTime, bill);
            }
        }

        return retMap;
    }

    private Map<String, ChargingBill> clacWaterSharedPrice(QueryChargingBill queryParam, CoChargingConfig chargingCfg) {
        Map<String, ChargingBill> retMap = new HashMap<>();
        // 参与公摊的分区ID
        Set<String> coSet = getCoSet(chargingCfg);

        // 水价公摊计算
        if (chargingCfg.getWaterGtComputeType() > 0 && coSet.contains(queryParam.getObjArea())) {
            QueryMeterReading param = new QueryMeterReading(queryParam.getAreaCode(), MeterObjTag.SHARED.getTagValue(),
                queryParam.getMeterCls(), queryParam.getStartTime(), queryParam.getEndTime());
            List<MeterReading> totalReadings = meterReadingMapper.selectByTag(param);
            Map<String, List<MeterReading>> groupedRecs = totalReadings.stream()
                .collect(Collectors.groupingBy(MeterReading::getMeterMonth));

            for (Map.Entry<String, List<MeterReading>> entry : groupedRecs.entrySet()) {
                String meterTime = entry.getKey();
                List<MeterReading> sharedReadings = entry.getValue();

                // 公摊价格计算
                Double price = clacSharedPrice(sharedReadings, coSet, queryParam.getObjArea(),
                    chargingCfg.getWaterGtComputeType(), chargingCfg.getWaterUnitPrice());

                ChargingBill bill = new ChargingBill();
                bill.setObjCode(queryParam.getObjArea());
                bill.setSharedPrice(price);
                bill.setSharedComputeType(chargingCfg.getElecGtComputeType());

                retMap.put(meterTime, bill);
            }

        }

        return retMap;
    }

    private Map<String, Long> getQuantityByTag(QueryChargingBill queryParam, MeterObjTag tag) {
        Map<String, Long> retMap = new HashMap<>();

        QueryMeterReading param = new QueryMeterReading(queryParam.getAreaCode(), tag.getTagValue(),
            queryParam.getMeterCls(), queryParam.getStartTime(), queryParam.getEndTime());
        List<MeterReading> totalReadings = meterReadingMapper.selectByTag(param);
        Map<String, List<MeterReading>> groupedRecs = totalReadings.stream()
            .collect(Collectors.groupingBy(MeterReading::getMeterMonth));

        for (Map.Entry<String, List<MeterReading>> entry : groupedRecs.entrySet()) {
            String meterTime = entry.getKey();
            List<MeterReading> sharedReadings = entry.getValue();

            // 公摊用量
            Long sharedQuantity = sumSharedReadings(sharedReadings);
            retMap.put(meterTime, sharedQuantity);
        }

        return retMap;
    }

    /**
     * 计算公摊价
     *
     * @return 价格
     */
    private Double clacSharedPrice(List<MeterReading> sharedReadings, Set<String> coSet, String objCode,
        int gtComputeType, Double unitPrice) {
        double sharedPrice = 0.0;

        // 公摊用量
        Long sharedQuantity = sumSharedReadings(sharedReadings);

        // 公摊总价
        Double sharedPriceSum = calcPrice(sharedQuantity, unitPrice);

        // 按户数公摊
        if (gtComputeType == 1) {
            sharedPrice = sharedPriceByCount(sharedPriceSum, coSet.size());
        }
        // 按面积公摊
        else if (gtComputeType == 2) {
            sharedPrice = sharedPriceByArea(sharedPriceSum, objCode, coSet);
        }

        return sharedPrice;
    }

    private Double sharedPriceByCount(Double price, int count) {
        BigDecimal bd1 = new BigDecimal(price.toString());
        BigDecimal bd2 = new BigDecimal(String.valueOf(count));
        BigDecimal res = bd1.divide(bd2, 3, RoundingMode.HALF_UP);
        return res.doubleValue();
    }

    private double sharedPriceByArea(Double sharedPriceSum, String objCode, Set<String> coSet) {
        List<AreaBuildingZoning> zonings = zoningService.selectByCodes(coSet);
        Map<String, AreaBuildingZoning> map = zonings.stream()
            .collect(Collectors.toMap(AreaBuildingZoning::getZoningCode, Function.identity()));
        AreaBuildingZoning zoning = map.get(objCode);

        Long areas = sumSharedAreas(zonings);

        BigDecimal bd1 = new BigDecimal(sharedPriceSum.toString());
        BigDecimal bd2 = new BigDecimal(areas.toString());
        BigDecimal bd3 = new BigDecimal(zoning.getBldgLdArea().toString());

        // 计算当前区块面积占总建筑面积的比例
        BigDecimal bl = bd3.divide(bd2, 3, RoundingMode.HALF_UP);
        BigDecimal res = bd1.multiply(bl);
        return res.doubleValue();
    }

    private Long sumSharedReadings(List<MeterReading> sharedReadings) {
        long sum = 0;
        for (MeterReading rec : sharedReadings) {
            sum += rec.getIncrease();
        }
        return sum;
    }

    private Long sumSharedAreas(List<AreaBuildingZoning> zonings) {
        long sum = 0;
        for (AreaBuildingZoning zo : zonings) {
            sum += zo.getBldgLdArea();
        }
        return sum;
    }

    /**
     * 计算用量总价
     *
     * @param meterCls  计量类别
     * @param dosage    用量
     * @param chargingCfg 价格配置
     * @return 返回总价
     */
    private Double calcPrice(int meterCls, Long dosage, CoChargingConfig chargingCfg) {
        if (meterCls == 45) {
            return calcPrice(dosage, chargingCfg.getElecUnitPrice());
        }
        else if (meterCls == 70) {
            return calcPrice(dosage, chargingCfg.getWaterUnitPrice());
        }

        return null;
    }

    /**
     * 计算用量总价
     *
     * @param dosage    用量
     * @param unitPrice 价格配置
     * @return 返回总价
     */
    private Double calcPrice(Long dosage, Double unitPrice) {
        BigDecimal bd1 = new BigDecimal(dosage.toString());
        BigDecimal bd2 = new BigDecimal(unitPrice.toString());
        BigDecimal res = bd1.multiply(bd2);
        return res.setScale(3, RoundingMode.HALF_UP).doubleValue();
    }

    private Double clacTotalPrice(ChargingBill bill) {
        if (null != bill.getMeteredPrice() && null != bill.getSharedPrice()) {
            BigDecimal bd1 = new BigDecimal(bill.getMeteredPrice().toString());
            BigDecimal bd2 = new BigDecimal(bill.getSharedPrice().toString());
            return bd1.add(bd2).setScale(3, RoundingMode.HALF_UP).doubleValue();
        }
        else if (null == bill.getMeteredPrice() && null != bill.getSharedPrice()) {
            return bill.getSharedPrice();
        }
        else if (null != bill.getMeteredPrice() && null == bill.getSharedPrice()) {
            return bill.getMeteredPrice();
        }
        else {
            return null;
        }
    }

    private Set<String> getCoSet(CoChargingConfig chargingCfg) {
        Set<String> coSet;

        if (null != chargingCfg) {
            coSet = chargingCfg.getRelCoList().stream().map(CoChargingConfigRel::getZoningCode)
                .collect(Collectors.toSet());
        }
        else {
            coSet = new HashSet<>();
        }

        return coSet;
    }

    private String getObjName(QueryChargingBill queryParam) {
        String objName = "未知";

        if (StringUtils.equals(AreaTier.Area.name(), queryParam.getTier())) {
            Area area = areaService.selectAreaByCode(queryParam.getObjArea());
            objName = area != null ? area.getShortName() : "未知";
        }
        else if (StringUtils.equals(AreaTier.Building.name(), queryParam.getTier())) {
            AreaBuilding building = buildingService.selectAreaBuildingByCode(queryParam.getObjArea());
            objName = (building != null) ? (building.getAreaShortName() + "/" + building.getBldgName()) : "未知";
        }
        else if (StringUtils.equals(AreaTier.Zoning.name(), queryParam.getTier())) {
            AreaBuildingZoning zoning = zoningService.selectAreaBuildingZoningByCode(queryParam.getObjArea());

            if (null != zoning) {
                AreaBuilding building = buildingService.selectAreaBuildingByCode(zoning.getBldgCode());
                objName = (building != null) ?
                    (building.getAreaShortName() + "/" + building.getBldgName() + "/" + zoning.getZoningName()) :
                    "未知";
            }
        }

        return objName;
    }
}