/*
 * 文 件 名:  ElecConsumeForecastService
 * 版    权:  华设设计集团股份有限公司
 * 描    述:  <描述>
 * 修 改 人:  lvwenbin
 * 修改时间:  2025/6/24
 * 跟踪单号:  <跟踪单号>
 * 修改单号:  <修改单号>
 * 修改内容:  <修改内容>
 */
package com.ruoyi.ems.service.analysis;

import com.huashe.common.domain.model.WeatherForecast;
import com.huashe.common.utils.DateUtils;
import com.ruoyi.ems.domain.Area;
import com.ruoyi.ems.domain.ElecConsumeForecast;
import com.ruoyi.ems.domain.ElecMeterH;
import com.ruoyi.ems.domain.EmsFacs;
import com.ruoyi.ems.domain.MeterBoundaryRel;
import com.ruoyi.ems.mapper.ElecConsumeForecastMapper;
import com.ruoyi.ems.model.QueryMeter;
import com.ruoyi.ems.service.IElecMeterHService;
import com.ruoyi.ems.service.IMeterBoundaryRelService;
import com.ruoyi.ems.service.IWeatherService;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
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.stream.Collectors;

/**
 * 用电消耗预测服务
 * <功能详细描述>
 *
 * @author lvwenbin
 * @version [版本号, 2025/6/24]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Service
public class ElecConsumeForecastService {
    public enum FacilityType {
        LIGHTING,        // 照明设施
        AIR_CONDITION,   // 空调设施
        PUBLIC,          // 公共设施
        OTHER            // 其他设施
    }

    private static final Logger log = LoggerFactory.getLogger(ElecConsumeForecastService.class);

    private final DateTimeFormatter dateForm = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    // 温度对空调用电的影响因子表
    private static final Map<Integer, Double> tempToAcFactor = new HashMap<>();

    // 光照时间对照明用电的影响因子表
    private static final Map<Integer, Double> lightHourToLightFactor = new HashMap<>();

    @Resource
    private ElecConsumeForecastMapper forecastMapper;

    @Resource
    private IMeterBoundaryRelService meterBoundaryRelService;

    @Resource
    private IElecMeterHService elecMeterHService;

    @Resource
    private IWeatherService weatherService;

    static {
        // 初始化空调影响因子（温度-影响因子映射）
        for (int i = 15; i <= 40; i++) {
            if (i < 22) {
                tempToAcFactor.put(i, 0.3 + (22 - i) * 0.05); // 15℃时0.65，22℃时0.3
            }
            else if (i <= 28) {
                tempToAcFactor.put(i, 0.3 + (i - 22) * 0.1); // 22℃时0.3，28℃时0.9
            }
            else {
                tempToAcFactor.put(i, 0.9 + (i - 28) * 0.1); // 28℃时0.9，40℃时2.1
            }
        }

        // 初始化照明影响因子（光照时间-影响因子映射，假设夏季光照时间长）
        for (int i = 8; i <= 16; i++) {
            lightHourToLightFactor.put(i, 1.5 - (i - 8) * 0.1); // 8小时光照时1.5，16小时时0.7
        }
    }

    // 地理区块用能预测
    public void forecastAreas(String adcode, String rootArea, Area area, LocalDate today, int forecastDay) {
        try {
            // 获取区块绑定的计量设备
            List<MeterBoundaryRel> boundaryRels = meterBoundaryRelService.selectRelByObj(area.getAreaCode(), 1, 45);

            if (CollectionUtils.isEmpty(boundaryRels)) {
                return;
            }

            Set<String> meterDevs = boundaryRels.stream()
                .map(MeterBoundaryRel::getMeterDevice)
                .collect(Collectors.toCollection(HashSet::new));

            // 获取历史数据 (过去30天)
            List<ElecMeterH> historicalData = getMeterDayList(rootArea, meterDevs, today);

            if (CollectionUtils.isEmpty(historicalData)) {
                return;
            }

            // 读取天气预报
            LocalDate forecastStart = today;
            LocalDate forecastEnd = today.plusDays(forecastDay - 1);
            List<WeatherForecast> weatherForecasts = weatherService.getWeatherForecast(adcode,
                forecastStart.format(dateForm), forecastEnd.format(dateForm));
            Map<String, WeatherForecast> weatherMap = convertToWeatherMap(weatherForecasts);

            List<ElecConsumeForecast> results = new ArrayList<>();

            while (forecastStart.isBefore(forecastEnd) || forecastStart.isEqual(forecastEnd)) {
                WeatherForecast weatherForecast = weatherMap.get(forecastStart.format(dateForm));
                double consumeForecast = calcAreaprediction(historicalData, weatherForecast);

                ElecConsumeForecast forecast = new ElecConsumeForecast();
                forecast.setAreaCode(rootArea);
                forecast.setObjCode(area.getAreaCode());
                forecast.setObjType(1);
                forecast.setDate(Date.from(forecastStart.atStartOfDay(ZoneId.systemDefault()).toInstant()));
                forecast.setElecUseQuantity(consumeForecast);

                results.add(forecast);
                forecastStart = forecastStart.plusDays(1);
            }

            forecastMapper.deleteByCondition(rootArea, 1, area.getAreaCode(), today.format(dateForm),
                forecastEnd.format(dateForm));
            forecastMapper.insertBatch(results);
        }
        catch (Exception e) {
            log.error("forecastAreas fail! areaCode:{}", area.getAreaCode(), e);
        }
    }

    // 设施用能预测
    public void forecastFacilities(String adcode, String rootArea, EmsFacs facs, LocalDate today, int forecastDay) {
        // 获取设施绑定的计量设备
        List<MeterBoundaryRel> boundaryRels = meterBoundaryRelService.selectRelByObj(facs.getFacsCode(), 2, 45);

        if (CollectionUtils.isEmpty(boundaryRels)) {
            return;
        }

        Set<String> meterDevs = boundaryRels.stream()
            .map(MeterBoundaryRel::getMeterDevice)
            .collect(Collectors.toCollection(HashSet::new));

        // 获取历史数据 (过去30天)
        List<ElecMeterH> historicalData = getMeterDayList(rootArea, meterDevs, today);

        if (CollectionUtils.isEmpty(historicalData)) {
            return;
        }

        // 读取天气预报
        LocalDate forecastStart = today;
        LocalDate forecastEnd = today.plusDays(forecastDay - 1);
        List<WeatherForecast> weatherForecasts = weatherService.getWeatherForecast(adcode,
            forecastStart.format(dateForm), forecastEnd.format(dateForm));
        Map<String, WeatherForecast> weatherMap = convertToWeatherMap(weatherForecasts);

        List<ElecConsumeForecast> results = new ArrayList<>();

        while (forecastStart.isBefore(forecastEnd) || forecastStart.isEqual(forecastEnd)) {
            WeatherForecast weatherForecast = weatherMap.get(forecastStart.format(dateForm));
            double consumeForecast = calcFacsPrediction(facs, historicalData, weatherForecast);

            ElecConsumeForecast forecast = new ElecConsumeForecast();
            forecast.setAreaCode(rootArea);
            forecast.setObjCode(facs.getFacsCode());
            forecast.setObjType(2);
            forecast.setDate(Date.from(forecastStart.atStartOfDay(ZoneId.systemDefault()).toInstant()));
            forecast.setElecUseQuantity(consumeForecast);

            results.add(forecast);
            forecastStart = forecastStart.plusDays(1);
        }

        forecastMapper.deleteByCondition(rootArea, 2, facs.getFacsCode(), today.format(dateForm),
            forecastEnd.format(dateForm));
        forecastMapper.insertBatch(results);
    }

    private Map<String, WeatherForecast> convertToWeatherMap(List<WeatherForecast> weatherForecasts) {
        Map<String, WeatherForecast> weatherMap = new HashMap<>();

        for (WeatherForecast weatherForecast : weatherForecasts) {
            String date = DateUtils.dateToString(weatherForecast.getDate(), "yyyy-MM-dd");
            weatherMap.put(date, weatherForecast);
        }

        return weatherMap;
    }

    private double calcAreaprediction(List<ElecMeterH> historicalData, WeatherForecast weather) {
        // 计算历史平均日用电量
        double totalConsumeElec = historicalData.stream().mapToDouble(ElecMeterH::getElecQuantity).sum();
        double historicalAvg = divideAndRound(totalConsumeElec, historicalData.size(), 2);

        // 获取天气影响因子（区块主要受空调影响）
        double weatherFactor = getAirConditionImpactFactor(weather);

        // 计算预测值
        return multiplyAndRound(historicalAvg, weatherFactor, 2);
    }

    private double calcFacsPrediction(EmsFacs emsFacs, List<ElecMeterH> historicalData, WeatherForecast weather) {
        FacilityType facilityType = determineFacilityType(emsFacs.getFacsSubCategory());

        // 计算历史平均日用电量
        double totalConsumeElec = historicalData.stream().mapToDouble(ElecMeterH::getElecQuantity).sum();
        double historicalAvg = divideAndRound(totalConsumeElec, historicalData.size(), 2);

        // 获取影响因子
        double factor = getFacilityImpactFactor(weather, facilityType);

        // 计算预测值
        return multiplyAndRound(historicalAvg, factor, 2);
    }

    private List<ElecMeterH> getMeterDayList(String rootArea, Set<String> meterDevs, LocalDate startDate) {
        LocalDate historyStart = startDate.minusDays(30);
        LocalDate historyEnd = startDate.minusDays(1);

        QueryMeter queryMeter = new QueryMeter();
        queryMeter.setAreaCode(rootArea);
        queryMeter.setDeviceCodes(meterDevs);
        queryMeter.setStartRecTime(historyStart.format(dateForm));
        queryMeter.setEndRecTime(historyEnd.format(dateForm));

        return elecMeterHService.staMeterDayList(queryMeter);
    }

    /**
     * 除法运算并保留指定小数位数
     *
     * @param dividend 被除数
     * @param divisor  除数
     * @param scale    保留小数位数
     * @return 四舍五入后的结果
     */
    private double divideAndRound(double dividend, double divisor, int scale) {
        if (divisor == 0) {
            return 0.0;
        }

        return BigDecimal.valueOf(dividend / divisor).setScale(scale, RoundingMode.HALF_UP).doubleValue();
    }

    // 获取空调设备的天气影响因子
    public double getAirConditionImpactFactor(WeatherForecast weather) {
        double temp = Math.max((weather.getDayTemp() != null) ? weather.getDayTemp() : 20,
            (weather.getNightTemp() != null) ? weather.getNightTemp() : 20);
        int tempInt = (int) Math.round(temp);
        return tempToAcFactor.getOrDefault(tempInt, 1.0);
    }

    private double getFacilityImpactFactor(WeatherForecast weather, FacilityType type) {
        switch (type) {
            case AIR_CONDITION:
                return getAirConditionImpactFactor(weather);
            case LIGHTING:
                return getLightingImpactFactor(weather);
            default:
                return 1.0; // 公共设施和其他设施默认影响因子为1
        }
    }

    private double getLightingImpactFactor(WeatherForecast weather) {
        LocalDate date = weather.getDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        int month = date.getMonthValue();
        int lightHours;

        // 简化处理：根据月份估算光照时间（实际应结合天气预报的日照时长）
        if (month >= 5 && month <= 8) {
            lightHours = 14; // 夏季光照时间长
        }
        else if (month >= 12 || month <= 2) {
            lightHours = 10; // 冬季光照时间短
        }
        else {
            lightHours = 12; // 春秋季
        }

        return lightHourToLightFactor.getOrDefault(lightHours, 1.0);
    }

    /**
     * 乘法运算并保留指定小数位数
     *
     * @param a     乘数1
     * @param b     乘数2
     * @param scale 保留小数位数
     * @return 四舍五入后的结果
     */
    private double multiplyAndRound(double a, double b, int scale) {
        return BigDecimal.valueOf(a * b).setScale(scale, RoundingMode.HALF_UP).doubleValue();
    }

    private FacilityType determineFacilityType(String subcategory) {
        if (subcategory.startsWith("Z010")) { // 照明
            return FacilityType.LIGHTING;
        }
        else if (subcategory.startsWith("Z020")) { // 空调
            return FacilityType.AIR_CONDITION;
        }
        else if (subcategory.startsWith("Z040")) { // 公共设施（充电桩等）
            return FacilityType.PUBLIC;
        }
        else {
            return FacilityType.OTHER;
        }
    }
}