package com.usky.vpp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.usky.common.core.bean.CommonPage;
import com.usky.common.core.exception.BusinessException;
import com.usky.common.security.utils.SecurityUtils;
import com.usky.vpp.domain.VppBiddingConfig;
import com.usky.vpp.domain.VppCustomer;
import com.usky.vpp.domain.VppDrEvent;
import com.usky.vpp.domain.VppDrInvitation;
import com.usky.vpp.domain.VppDrSubsidyPrediction;
import com.usky.vpp.domain.VppResponseDeviationPrice;
import com.usky.vpp.domain.VppSite;
import com.usky.vpp.mapper.VppCustomerMapper;
import com.usky.vpp.mapper.VppDrEventMapper;
import com.usky.vpp.mapper.VppDrInvitationMapper;
import com.usky.vpp.mapper.VppDrSubsidyPredictionMapper;
import com.usky.vpp.mapper.VppSiteMapper;
import com.usky.vpp.service.VppBaselineService;
import com.usky.vpp.service.VppDrSubsidyPredictionService;
import com.usky.vpp.service.VppSubsidyPredictionCalculator;
import com.usky.vpp.service.vo.DrSubsidyPredictionCalculateRequest;
import com.usky.vpp.service.vo.DrSubsidyPredictionRequest;
import com.usky.vpp.service.vo.DrSubsidyPredictionVO;
import com.usky.vpp.service.vo.SiteDeclaredCapacityVO;
import com.usky.vpp.service.vo.SubsidyPredictionCalcResult;
import com.usky.vpp.util.VppAuditHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 补贴金额预测 业务实现
 */
@Service
public class VppDrSubsidyPredictionServiceImpl implements VppDrSubsidyPredictionService {

    private static final Logger log = LoggerFactory.getLogger(VppDrSubsidyPredictionServiceImpl.class);
    private static final DateTimeFormatter DATE_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Autowired
    private VppDrSubsidyPredictionMapper subsidyPredictionMapper;
    @Autowired
    private VppDrInvitationMapper invitationMapper;
    @Autowired
    private VppDrEventMapper eventMapper;
    @Autowired
    private VppSiteMapper siteMapper;
    @Autowired
    private VppCustomerMapper customerMapper;
    @Autowired
    private VppBaselineService vppBaselineService;
    @Autowired
    private VppSubsidyPredictionCalculator subsidyPredictionCalculator;

    @Override
    public CommonPage<DrSubsidyPredictionVO> page(String siteName, String customerName,
                                                   String startTime, String endTime,
                                                   Integer current, Integer size) {
        Page<VppDrSubsidyPrediction> page = new Page<>(
                current != null ? current : 1,
                size != null && size <= 100 ? size : 20);

        LambdaQueryWrapper<VppDrSubsidyPrediction> wrapper = new LambdaQueryWrapper<>();
        Integer tenantId = SecurityUtils.getTenantId();
        wrapper.eq(tenantId != null, VppDrSubsidyPrediction::getTenantId, tenantId);
        if (StringUtils.hasText(siteName)) {
            wrapper.like(VppDrSubsidyPrediction::getSiteName, siteName);
        }
        if (StringUtils.hasText(customerName)) {
            wrapper.like(VppDrSubsidyPrediction::getCustomerName, customerName);
        }
        if (StringUtils.hasText(startTime)) {
            wrapper.ge(VppDrSubsidyPrediction::getEventStartTime, startTime);
        }
        if (StringUtils.hasText(endTime)) {
            wrapper.le(VppDrSubsidyPrediction::getEventStartTime, endTime);
        }
        wrapper.orderByDesc(VppDrSubsidyPrediction::getCreateTime);

        Page<VppDrSubsidyPrediction> result = subsidyPredictionMapper.selectPage(page, wrapper);
        Map<Long, String> invitationNoMap = loadInvitationNoMap(result.getRecords());
        List<DrSubsidyPredictionVO> list = result.getRecords().stream()
                .map(item -> toVo(item, invitationNoMap))
                .collect(Collectors.toList());

        return new CommonPage<>(list, result.getTotal(), size != null ? size : 20, current != null ? current : 1);
    }

    @Override
    public DrSubsidyPredictionVO calculate(DrSubsidyPredictionCalculateRequest request) {
        if (request == null) {
            throw new BusinessException("请求参数不能为空");
        }
        if (!StringUtils.hasText(request.getInvitationNo())) {
            throw new BusinessException("邀约编号不能为空");
        }
        if (request.getActualResponseGridKw() == null) {
            throw new BusinessException("实际响应容量（电网）不能为空");
        }

        VppDrInvitation invitation = findInvitationByNo(request.getInvitationNo().trim());
        VppDrEvent event = invitation.getDrEventId() == null ? null : eventMapper.selectById(invitation.getDrEventId());
        if (event == null || VppAuditHelper.isDeleted(event.getDeleteFlag())) {
            throw new BusinessException("邀约关联的需求响应事件不存在");
        }

        VppDrSubsidyPrediction entity = new VppDrSubsidyPrediction();
        entity.setInvitationId(invitation.getId());
        entity.setDrEventId(event.getId());
        entity.setSiteId(invitation.getSiteId());
        fillSiteName(entity);
        entity.setCustomerId(invitation.getCustomerId());
        fillCustomerName(entity);
        entity.setEventStartTime(event.getStartTime());
        entity.setEventEndTime(event.getEndTime());

        BigDecimal declaredKw = invitation.getDeclaredCapacityKw() != null
                ? invitation.getDeclaredCapacityKw()
                : invitation.getActualClearedCapacityKw();
        BigDecimal actualClearedKw = invitation.getActualClearedCapacityKw();
        BigDecimal platformKw = resolvePlatformActualResponseKw(
                invitation.getSiteId(), event.getStartTime(), event.getEndTime(),
                invitation.getEstimatedResponseCapacityKw());

        entity.setActualResponseGridKw(request.getActualResponseGridKw());
        entity.setActualResponsePlatformKw(platformKw);
        entity.setDeclaredClearedKw(declaredKw);
        entity.setEstimatedCapacityKw(invitation.getEstimatedResponseCapacityKw());
        entity.setClearedCapacityKw(actualClearedKw);
        entity.setTenantId(event.getTenantId() != null ? event.getTenantId() : invitation.getTenantId());

        applyCalcResult(entity, subsidyPredictionCalculator.calculate(invitation, event));

        Map<Long, String> invitationNoMap = Collections.singletonMap(invitation.getId(), invitation.getInvitationNo());
        return toVo(entity, invitationNoMap);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean create(DrSubsidyPredictionRequest request) {
        if (request == null) {
            throw new BusinessException("请求参数不能为空");
        }
        assertInvitationNotDuplicated(request.getInvitationId(), null);

        VppDrSubsidyPrediction entity = new VppDrSubsidyPrediction();
        applyRequest(entity, request);
        fillCustomerFromInvitationIfAbsent(entity);
        fillSiteName(entity);
        fillCustomerName(entity);
        entity.setTenantId(SecurityUtils.getTenantId());
        entity.setCreateTime(LocalDateTime.now());
        entity.setUpdateTime(LocalDateTime.now());
        return subsidyPredictionMapper.insert(entity) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(DrSubsidyPredictionRequest request) {
        if (request == null || request.getId() == null) {
            throw new BusinessException("主键ID不能为空");
        }
        VppDrSubsidyPrediction existing = subsidyPredictionMapper.selectById(request.getId());
        if (existing == null) {
            throw new BusinessException("补贴预测记录不存在");
        }
        Integer tenantId = SecurityUtils.getTenantId();
        if (tenantId != null && existing.getTenantId() != null && !tenantId.equals(existing.getTenantId())) {
            throw new BusinessException("补贴预测记录不存在");
        }
        Long invitationId = request.getInvitationId() != null
                ? request.getInvitationId()
                : existing.getInvitationId();
        assertInvitationNotDuplicated(invitationId, existing.getId());

        applyRequest(existing, request);
        if (request.getSiteId() != null) {
            fillSiteName(existing);
        }
        if (request.getCustomerId() != null) {
            fillCustomerName(existing);
        }
        existing.setUpdateTime(LocalDateTime.now());
        return subsidyPredictionMapper.updateById(existing) > 0;
    }

    /**
     * 同一邀约不可重复新增补贴预测。
     */
    private void assertInvitationNotDuplicated(Long invitationId, Long excludeId) {
        if (invitationId == null) {
            return;
        }
        Integer tenantId = SecurityUtils.getTenantId();
        Integer count = subsidyPredictionMapper.selectCount(
                new LambdaQueryWrapper<VppDrSubsidyPrediction>()
                        .eq(VppDrSubsidyPrediction::getInvitationId, invitationId)
                        .eq(tenantId != null, VppDrSubsidyPrediction::getTenantId, tenantId)
                        .ne(excludeId != null, VppDrSubsidyPrediction::getId, excludeId));
        if (count != null && count > 0) {
            throw new BusinessException("该邀约事件已添加补贴金额预测，不能重复添加！");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean delete(Long id) {
        if (id == null) {
            throw new BusinessException("主键ID不能为空");
        }
        VppDrSubsidyPrediction existing = subsidyPredictionMapper.selectById(id);
        if (existing == null) {
            throw new BusinessException("补贴预测记录不存在");
        }
        Integer tenantId = SecurityUtils.getTenantId();
        if (tenantId != null && existing.getTenantId() != null && !tenantId.equals(existing.getTenantId())) {
            throw new BusinessException("补贴预测记录不存在");
        }
        return subsidyPredictionMapper.deleteById(id) > 0;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void generateByEventId(Long drEventId) {
        if (drEventId == null) {
            throw new BusinessException("事件ID不能为空");
        }

        VppDrEvent event = eventMapper.selectById(drEventId);
        if (event == null || VppAuditHelper.isDeleted(event.getDeleteFlag())) {
            log.warn("补贴预测生成跳过：事件不存在 drEventId={}", drEventId);
            return;
        }

        List<VppDrInvitation> invitations = invitationMapper.selectList(
                new LambdaQueryWrapper<VppDrInvitation>()
                        .eq(VppDrInvitation::getDrEventId, drEventId)
                        .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
        );
        if (invitations.isEmpty()) {
            log.info("补贴预测生成跳过：事件下无邀约 drEventId={}", drEventId);
            return;
        }

        Set<Long> siteIds = invitations.stream()
                .map(VppDrInvitation::getSiteId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, String> siteNameMap = siteIds.isEmpty() ? Collections.emptyMap()
                : siteMapper.selectBatchIds(siteIds).stream()
                        .collect(Collectors.toMap(VppSite::getId, VppSite::getSiteName, (a, b) -> a));

        Set<Long> customerIds = invitations.stream()
                .map(VppDrInvitation::getCustomerId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, String> customerNameMap = loadCustomerNameMap(customerIds);

        List<VppResponseDeviationPrice> adjustments = subsidyPredictionCalculator.loadAdjustments(event.getTenantId());
        List<VppBiddingConfig> biddingConfigs =
                subsidyPredictionCalculator.loadBiddingConfigs(event.getTenantId());

        for (VppDrInvitation invitation : invitations) {
            VppDrSubsidyPrediction prediction = buildPrediction(
                    event, invitation, siteNameMap, customerNameMap, adjustments, biddingConfigs);

            VppDrSubsidyPrediction existing = subsidyPredictionMapper.selectOne(
                    new LambdaQueryWrapper<VppDrSubsidyPrediction>()
                            .eq(VppDrSubsidyPrediction::getInvitationId, invitation.getId())
            );
            if (existing != null) {
                prediction.setId(existing.getId());
                prediction.setCreateTime(existing.getCreateTime());
                prediction.setUpdateTime(LocalDateTime.now());
                subsidyPredictionMapper.updateById(prediction);
            } else {
                prediction.setCreateTime(LocalDateTime.now());
                prediction.setUpdateTime(LocalDateTime.now());
                subsidyPredictionMapper.insert(prediction);
            }
        }

        log.info("补贴预测生成完成 drEventId={}, invitationCount={}", drEventId, invitations.size());
    }

    private VppDrSubsidyPrediction buildPrediction(VppDrEvent event, VppDrInvitation invitation,
                                                    Map<Long, String> siteNameMap,
                                                    Map<Long, String> customerNameMap,
                                                    List<VppResponseDeviationPrice> adjustments,
                                                    List<VppBiddingConfig> biddingConfigs) {
        VppDrSubsidyPrediction p = new VppDrSubsidyPrediction();
        p.setInvitationId(invitation.getId());
        p.setDrEventId(event.getId());
        p.setSiteId(invitation.getSiteId());
        p.setSiteName(invitation.getSiteId() == null ? null : siteNameMap.get(invitation.getSiteId()));
        p.setCustomerId(invitation.getCustomerId());
        p.setCustomerName(invitation.getCustomerId() == null ? null : customerNameMap.get(invitation.getCustomerId()));
        p.setEventStartTime(event.getStartTime());
        p.setEventEndTime(event.getEndTime());

        BigDecimal platformKw = resolvePlatformActualResponseKw(
                invitation.getSiteId(), event.getStartTime(), event.getEndTime(),
                invitation.getEstimatedResponseCapacityKw());
        BigDecimal gridKw = invitation.getActualResponseCapacityKw();
        BigDecimal declaredKw = invitation.getDeclaredCapacityKw() != null
                ? invitation.getDeclaredCapacityKw()
                : invitation.getActualClearedCapacityKw();
        BigDecimal actualClearedKw = invitation.getActualClearedCapacityKw();

        p.setActualResponsePlatformKw(platformKw);
        p.setActualResponseGridKw(gridKw);
        p.setDeclaredClearedKw(declaredKw);
        p.setEstimatedCapacityKw(invitation.getEstimatedResponseCapacityKw());
        p.setClearedCapacityKw(actualClearedKw);
        p.setTenantId(event.getTenantId());
        applyCalcResult(p, subsidyPredictionCalculator.calculate(
                invitation, event, adjustments, biddingConfigs));
        return p;
    }

    /**
     * 平台实际响应量：复用申报容量链路（可调峰资源点→设备→TSDB 响应时段有功功率均值）。
     * TSDB 无数据时回退邀约上的预估响应容量。
     */
    private BigDecimal resolvePlatformActualResponseKw(Long siteId,
                                                       LocalDateTime startTime,
                                                       LocalDateTime endTime,
                                                       BigDecimal fallbackKw) {
        if (siteId == null || startTime == null || endTime == null || !endTime.isAfter(startTime)) {
            return fallbackKw;
        }
        try {
            SiteDeclaredCapacityVO capacity = vppBaselineService.getSiteDeclaredCapacity(
                    siteId,
                    startTime.format(DATE_TIME_FMT),
                    endTime.format(DATE_TIME_FMT));
            if (capacity != null && capacity.getEstimatedResponseCapacityKw() != null
                    && capacity.getEstimatedResponseCapacityKw().compareTo(BigDecimal.ZERO) > 0) {
                return capacity.getEstimatedResponseCapacityKw();
            }
        } catch (Exception ex) {
            log.warn("查询平台实际响应量失败，回退邀约预估容量 siteId={}, err={}", siteId, ex.getMessage());
        }
        return fallbackKw;
    }

    private VppDrInvitation findInvitationByNo(String invitationNo) {
        Integer tenantId = SecurityUtils.getTenantId();
        VppDrInvitation invitation = invitationMapper.selectOne(
                new LambdaQueryWrapper<VppDrInvitation>()
                        .eq(VppDrInvitation::getInvitationNo, invitationNo)
                        .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                        .eq(tenantId != null, VppDrInvitation::getTenantId, tenantId)
                        .last("LIMIT 1"));
        if (invitation == null) {
            throw new BusinessException("邀约不存在：" + invitationNo);
        }
        return invitation;
    }

    private void applyRequest(VppDrSubsidyPrediction entity, DrSubsidyPredictionRequest request) {
        if (request.getInvitationId() != null) {
            entity.setInvitationId(request.getInvitationId());
        }
        if (request.getDrEventId() != null) {
            entity.setDrEventId(request.getDrEventId());
        }
        if (request.getSiteId() != null) {
            entity.setSiteId(request.getSiteId());
        }
        if (request.getCustomerId() != null) {
            entity.setCustomerId(request.getCustomerId());
        }
        if (StringUtils.hasText(request.getCustomerName())) {
            entity.setCustomerName(request.getCustomerName().trim());
        }
        if (request.getEventStartTime() != null) {
            entity.setEventStartTime(request.getEventStartTime());
        }
        if (request.getEventEndTime() != null) {
            entity.setEventEndTime(request.getEventEndTime());
        }
        if (request.getSubsidyPrice() != null) {
            entity.setSubsidyPrice(request.getSubsidyPrice());
        }
        if (request.getActualResponseGridKw() != null) {
            entity.setActualResponseGridKw(request.getActualResponseGridKw());
        }
        if (request.getActualResponsePlatformKw() != null) {
            entity.setActualResponsePlatformKw(request.getActualResponsePlatformKw());
            entity.setEstimatedCapacityKw(request.getActualResponsePlatformKw());
        }
        if (request.getDeclaredClearedKw() != null) {
            entity.setDeclaredClearedKw(request.getDeclaredClearedKw());
            entity.setClearedCapacityKw(request.getDeclaredClearedKw());
        }
        if (request.getShareRatio() != null) {
            entity.setShareRatio(normalizeShareRatio(request.getShareRatio()));
        }
        if (request.getResponseDurationMin() != null) {
            entity.setResponseDurationMin(request.getResponseDurationMin());
        }
        if (request.getDeviationRate() != null) {
            entity.setDeviationRate(request.getDeviationRate());
        }
        if (request.getCompletionRate() != null) {
            entity.setCompletionRate(request.getCompletionRate());
        }
        if (request.getPriceAdjustmentCoefficient() != null) {
            entity.setPriceAdjustmentCoefficient(request.getPriceAdjustmentCoefficient());
        }
        if (request.getEstimatedSubsidyAmount() != null) {
            entity.setEstimatedSubsidyAmount(request.getEstimatedSubsidyAmount());
        }
    }

    private void fillCustomerFromInvitationIfAbsent(VppDrSubsidyPrediction entity) {
        if (entity.getCustomerId() != null || entity.getInvitationId() == null) {
            return;
        }
        VppDrInvitation invitation = invitationMapper.selectById(entity.getInvitationId());
        if (invitation != null) {
            entity.setCustomerId(invitation.getCustomerId());
        }
    }

    private void fillSiteName(VppDrSubsidyPrediction entity) {
        if (entity.getSiteId() == null) {
            entity.setSiteName(null);
            return;
        }
        VppSite site = siteMapper.selectById(entity.getSiteId());
        entity.setSiteName(site != null ? site.getSiteName() : null);
    }

    private void fillCustomerName(VppDrSubsidyPrediction entity) {
        if (entity.getCustomerId() == null) {
            if (!StringUtils.hasText(entity.getCustomerName())) {
                entity.setCustomerName(null);
            }
            return;
        }
        if (StringUtils.hasText(entity.getCustomerName())) {
            return;
        }
        VppCustomer customer = customerMapper.selectById(entity.getCustomerId());
        entity.setCustomerName(customer != null ? customer.getCustomerName() : null);
    }

    private Map<Long, String> loadCustomerNameMap(Set<Long> customerIds) {
        if (customerIds == null || customerIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return customerMapper.selectBatchIds(customerIds).stream()
                .collect(Collectors.toMap(VppCustomer::getId, VppCustomer::getCustomerName, (a, b) -> a));
    }

    private void applyCalcResult(VppDrSubsidyPrediction entity, SubsidyPredictionCalcResult calc) {
        entity.setResponseDurationMin(calc.getResponseDurationMin());
        entity.setPriceAdjustmentCoefficient(calc.getPriceAdjustmentCoefficient());
        entity.setCompletionRate(calc.getCompletionRate());
        entity.setDeviationRate(calc.getDeviationRate());
        entity.setSubsidyPrice(calc.getSubsidyPrice());
        entity.setEstimatedSubsidyAmount(calc.getEstimatedSubsidyAmount());
    }

    private Map<Long, String> loadInvitationNoMap(List<VppDrSubsidyPrediction> predictions) {
        Set<Long> invitationIds = predictions.stream()
                .map(VppDrSubsidyPrediction::getInvitationId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (invitationIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return invitationMapper.selectBatchIds(invitationIds).stream()
                .collect(Collectors.toMap(VppDrInvitation::getId, VppDrInvitation::getInvitationNo,
                        (left, right) -> left));
    }

    /** 分成比例统一为 0~1；若传入值 &gt; 1 则按百分数换算 */
    private BigDecimal normalizeShareRatio(BigDecimal raw) {
        if (raw == null) {
            return null;
        }
        if (raw.compareTo(BigDecimal.ONE) > 0) {
            return raw.divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
        }
        return raw;
    }

    private DrSubsidyPredictionVO toVo(VppDrSubsidyPrediction entity, Map<Long, String> invitationNoMap) {
        DrSubsidyPredictionVO vo = new DrSubsidyPredictionVO();
        vo.setId(entity.getId());
        vo.setInvitationId(entity.getInvitationId());
        vo.setInvitationNo(invitationNoMap.get(entity.getInvitationId()));
        vo.setDrEventId(entity.getDrEventId());
        vo.setSiteId(entity.getSiteId());
        vo.setSiteName(entity.getSiteName());
        vo.setCustomerId(entity.getCustomerId());
        vo.setCustomerName(entity.getCustomerName());
        vo.setEventStartTime(entity.getEventStartTime());
        vo.setEventEndTime(entity.getEventEndTime());
        vo.setResponseDurationMin(entity.getResponseDurationMin());
        vo.setSubsidyPrice(entity.getSubsidyPrice());
        vo.setEstimatedCapacityKw(entity.getEstimatedCapacityKw());
        vo.setClearedCapacityKw(entity.getClearedCapacityKw());
        vo.setActualResponseGridKw(entity.getActualResponseGridKw());
        vo.setActualResponsePlatformKw(entity.getActualResponsePlatformKw());
        vo.setDeclaredClearedKw(entity.getDeclaredClearedKw());
        vo.setShareRatio(entity.getShareRatio());
        vo.setPriceAdjustmentCoefficient(entity.getPriceAdjustmentCoefficient());
        vo.setCompletionRate(entity.getCompletionRate());
        vo.setDeviationRate(entity.getDeviationRate());
        vo.setEstimatedSubsidyAmount(entity.getEstimatedSubsidyAmount());
        vo.setCreateTime(entity.getCreateTime());
        vo.setUpdateTime(entity.getUpdateTime());
        return vo;
    }
}
