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

import com.huashe.common.domain.model.WeatherForecast;
import com.huashe.common.utils.DateUtils;
import com.ruoyi.ems.domain.ElecProdForecast;
import com.ruoyi.ems.mapper.ElecProdForecastMapper;
import com.ruoyi.ems.model.PvSupplyVO;
import com.ruoyi.ems.model.QueryMeter;
import com.ruoyi.ems.service.IElecPvSupplyHService;
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.List;
import java.util.Map;

/**
 * 产能预测
 * <功能详细描述>
 *
 * @author lvwenbin
 * @version [版本号, 2025/6/19]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Service
public class ElecProdForecastService {
    private static final Logger log = LoggerFactory.getLogger(ElecProdForecastService.class);

    // 天气影响因子
    private final static Map<String, Double> weatherFactorMap = new HashMap<>();

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

    /**
     * 光伏产能预测数据服务
     */
    @Resource
    private IElecPvSupplyHService pvSupplyHService;

    /**
     * 天气服务
     */
    @Resource
    private IWeatherService weatherService;

    @Resource
    private ElecProdForecastMapper forecastMapper;

    static {
        // 初始化默认影响因子
        weatherFactorMap.put("1101", 1.0);   // 晴
        weatherFactorMap.put("1103", 0.8);   // 多云
        weatherFactorMap.put("1106", 0.4);   // 阴天
        weatherFactorMap.put("2100", 0.5);   // 雨
        weatherFactorMap.put("2101", 0.4);   // 小雨
        weatherFactorMap.put("2102", 0.4);   // 中雨
        weatherFactorMap.put("2103", 0.5);   // 大雨
        weatherFactorMap.put("2104", 0.6);   // 暴雨
        weatherFactorMap.put("2111", 0.8);   // 阵雨
        weatherFactorMap.put("2121", 0.8);   // 雷阵雨
        weatherFactorMap.put("2200", 0.3);   // 雪
        weatherFactorMap.put("2201", 0.3);   // 小雪
        weatherFactorMap.put("2202", 0.3);   // 中雪
        weatherFactorMap.put("2203", 0.2);   // 大雪
        weatherFactorMap.put("2204", 0.1);   // 暴雪
        weatherFactorMap.put("2211", 0.4);   // 雨夹雪
        weatherFactorMap.put("4101", 0.8);   // 轻雾
        weatherFactorMap.put("4102", 0.7);   // 大雾
        weatherFactorMap.put("4103", 0.6);   // 浓雾
        weatherFactorMap.put("4200", 0.5);   // 霾
        weatherFactorMap.put("4301", 0.5);   // 浮尘
        weatherFactorMap.put("4302", 0.4);   // 扬沙
        weatherFactorMap.put("4303", 0.3);   // 沙尘暴
    }

    /**
     * 执行光伏产能预测
     *
     * @param areaCode    园区代码
     * @param facsCode    设施编码
     * @param today       当前日期
     * @param forecastDay 预测天数
     */
    public void executePvForecast(String adcode, String areaCode, String facsCode, LocalDate today, int forecastDay) {

        try {
            // 1. 读取历史数据 (使用过去30天的数据作为训练集)
            LocalDate historyStart = today.minusDays(30);
            LocalDate historyEnd = today.minusDays(1);
            QueryMeter queryMeter = new QueryMeter(areaCode, facsCode, historyStart.format(dateForm),
                historyEnd.format(dateForm));
            queryMeter.setTimeDimension("day");
            List<PvSupplyVO> historicalData = pvSupplyHService.selectPvSupplyList(queryMeter);

            if (historicalData.isEmpty()) {
                // 没有历史数据，无法进行预测
                return;
            }

            // 2. 读取天气预报
            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<ElecProdForecast> result = new ArrayList<>();

            while (forecastStart.isBefore(forecastEnd) || forecastStart.isEqual(forecastEnd)) {
                WeatherForecast weatherForecast = weatherMap.get(forecastStart.format(dateForm));
                ElecProdForecast elecProdForecast = predictDailyProduction(areaCode, facsCode, forecastStart,
                    historicalData, weatherForecast);
                result.add(elecProdForecast);
                forecastStart = forecastStart.plusDays(1);
            }

            // 保存数据到数据库
            forecastMapper.deleteByCondition(areaCode, facsCode, today.format(dateForm), forecastEnd.format(dateForm));

            if (CollectionUtils.isNotEmpty(result)) {
                forecastMapper.insertBatch(result);
            }
        }
        catch (Exception e) {
            log.error("execute Pv forecast fail! areaCode:{}, facsCode:{}", areaCode, facsCode, e);
        }
    }

    private ElecProdForecast predictDailyProduction(String areaCode, String facsCode, LocalDate date,
        List<PvSupplyVO> trainingData, WeatherForecast weatherForecast) {
        // 计算历史平均日发电量（保留两位小数）
        double totalGenElec = trainingData.stream().mapToDouble(PvSupplyVO::getGenElecQuantityValue).sum();
        double historicalAvgDailyProduction = divideAndRound(totalGenElec, trainingData.size(), 2);

        // 计算历史平均上网电量（保留两位小数）
        double totalUpElec = trainingData.stream().mapToDouble(PvSupplyVO::getUpElecQuantityValue).sum();
        double upElecQuantity = divideAndRound(totalUpElec, trainingData.size(), 2);

        // 获取天气影响因子
        double weatherFactor = getWeatherFactor(weatherForecast);

        // 计算预测值 = 历史平均值 * 天气影响因子
        double forecastProduction = multiplyAndRound(historicalAvgDailyProduction, weatherFactor, 2);

        // 计算平均功率 (kW)
        double avgPower = divideAndRound(forecastProduction, 24.0, 2);

        ElecProdForecast forecast = new ElecProdForecast();
        forecast.setAreaCode(areaCode);
        forecast.setFacsCode(facsCode);
        forecast.setDate(Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant()));
        forecast.setElecProdQuantity(forecastProduction);
        forecast.setAvgPower(avgPower);
        forecast.setUpElecQuantity(upElecQuantity);
        return forecast;
    }

    private double getWeatherFactor(WeatherForecast forecast) {
        Double factor = null;

        if (null != forecast) {
            // 优先使用日间天气
            String weatherCode = forecast.getDayWeatherType();
            factor = weatherFactorMap.get(weatherCode);

            // 如果日间天气没有影响因子，使用夜间天气
            if (factor == null) {
                weatherCode = forecast.getNightWeatherType();
                factor = weatherFactorMap.get(weatherCode);
            }

            // 如果仍然没有，使用默认值
            factor = factor != null ? factor : 0.8;
        }
        else {
            factor = 0.8;
        }

        return factor;
    }

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

    /**
     * 除法运算并保留指定小数位数
     * @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();
    }

    /**
     * 乘法运算并保留指定小数位数
     * @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();
    }
}