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.constant.VppDrEventStatus;
import com.usky.vpp.domain.VppCustomer;
import com.usky.vpp.domain.VppDrEvaluation;
import com.usky.vpp.domain.VppDrEvent;
import com.usky.vpp.domain.VppDrExecution;
import com.usky.vpp.domain.VppDrInvitation;
import com.usky.vpp.domain.VppDrParticipation;
import com.usky.vpp.domain.VppDrStrategy;
import com.usky.vpp.domain.VppDrStrategyResource;
import com.usky.vpp.domain.VppResourcePoint;
import com.usky.vpp.domain.VppSite;
import com.usky.vpp.mapper.VppCustomerMapper;
import com.usky.vpp.mapper.VppDrEvaluationMapper;
import com.usky.vpp.mapper.VppDrEventMapper;
import com.usky.vpp.mapper.VppDrExecutionMapper;
import com.usky.vpp.mapper.VppDrInvitationMapper;
import com.usky.vpp.mapper.VppDrParticipationMapper;
import com.usky.vpp.mapper.VppDrStrategyMapper;
import com.usky.vpp.mapper.VppDrStrategyResourceMapper;
import com.usky.vpp.mapper.VppResourcePointMapper;
import com.usky.vpp.mapper.VppSiteMapper;
import com.usky.vpp.service.VppDrService;
import com.usky.vpp.service.VppDrSubsidyPredictionService;
import com.usky.vpp.service.VppUnIntegrationService;
import com.usky.vpp.service.vo.DrClearingRequest;
import com.usky.vpp.service.vo.DrEventDetailVO;
import com.usky.vpp.service.vo.DrEventRequest;
import com.usky.vpp.service.vo.DrEventStatusRequest;
import com.usky.vpp.service.vo.DrParticipationVO;
import com.usky.vpp.service.vo.DrInterveneRequest;
import com.usky.vpp.service.vo.DrParticipateRequest;
import com.usky.vpp.service.vo.DrStrategyRequest;
import com.usky.vpp.service.vo.DrStrategyVO;
import com.usky.vpp.util.VppAuditHelper;
import com.usky.vpp.util.VppDrParticipationHelper;
import com.usky.vpp.util.VppPageHelper;
import com.usky.vpp.util.VppSiteResourceHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
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.util.*;
import java.util.stream.Collectors;

@Service
public class VppDrServiceImpl implements VppDrService {

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

    private static final int EVENT_STATUS_PENDING = VppDrEventStatus.PENDING;
    private static final int EVENT_STATUS_DECLARED = VppDrEventStatus.DECLARED;
    private static final int EVENT_STATUS_EXECUTING = VppDrEventStatus.EXECUTING;
    private static final int EVENT_STATUS_ENDED = VppDrEventStatus.ENDED;
    private static final int EVENT_STATUS_CANCELLED = VppDrEventStatus.CANCELLED;
    private static final int EVENT_STATUS_SPLITTING = VppDrEventStatus.LAST_PLACE_SPLITTING;

    private static final int PARTICIPATE_STATUS_ACCEPT = 1;
    private static final int REPLY_ACCEPT = 1;
    private static final int REPLY_REJECT = 2;

    private static final int EXECUTE_STATUS_PENDING = 0;
    private static final int EXECUTE_STATUS_RUNNING = 1;
    private static final int EXECUTE_STATUS_FINISHED = 2;

    @Autowired
    private VppDrEventMapper eventMapper;
    @Autowired
    private VppDrParticipationMapper participationMapper;
    @Autowired
    private VppDrInvitationMapper invitationMapper;
    @Autowired
    private VppCustomerMapper customerMapper;
    @Autowired
    private VppDrStrategyMapper strategyMapper;
    @Autowired
    private VppDrStrategyResourceMapper strategyResourceMapper;
    @Autowired
    private VppDrExecutionMapper executionMapper;
    @Autowired
    private VppDrEvaluationMapper evaluationMapper;
    @Autowired
    private VppResourcePointMapper resourcePointMapper;
    @Autowired
    private VppSiteMapper siteMapper;
    @Autowired
    private VppSiteResourceHelper siteResourceHelper;
    @Autowired
    private VppUnIntegrationService unIntegrationService;
    @Autowired
    private VppDrSubsidyPredictionService subsidyPredictionService;

    @Override
    public CommonPage<VppDrEvent> pageEvent(Map<String, Object> params) {
        Page<VppDrEvent> page = VppPageHelper.of(params);
        LambdaQueryWrapper<VppDrEvent> wrapper = new LambdaQueryWrapper<VppDrEvent>()
                .eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .orderByDesc(VppDrEvent::getStartTime);

        if (params != null) {
            if (params.get("eventStatus") != null) {
                wrapper.eq(VppDrEvent::getEventStatus, Integer.parseInt(params.get("eventStatus").toString()));
            }
            if (params.get("responseType") != null) {
                wrapper.eq(VppDrEvent::getResponseType, Integer.parseInt(params.get("responseType").toString()));
            }
            if (params.get("eventType") != null) {
                wrapper.eq(VppDrEvent::getEventType, Integer.parseInt(params.get("eventType").toString()));
            }
            if (params.get("startTime") != null) {
                wrapper.ge(VppDrEvent::getStartTime, params.get("startTime").toString());
            }
            if (params.get("endTime") != null) {
                wrapper.le(VppDrEvent::getStartTime, params.get("endTime").toString());
            }
        }

        Page<VppDrEvent> result = eventMapper.selectPage(page, wrapper);
        return new CommonPage<>(result.getRecords(), result.getTotal(), result.getCurrent(), result.getSize());
    }

    @Override
    public DrEventDetailVO getEvent(Long id) {
        VppDrEvent event = requireEvent(id);
        return toEventDetailVo(event);
    }

    private VppDrEvent requireEvent(Long id) {
        VppDrEvent event = eventMapper.selectById(id);
        if (event == null || VppAuditHelper.isDeleted(event.getDeleteFlag())) {
            throw new BusinessException("需求响应事件不存在");
        }
        return event;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public VppDrEvent createEvent(DrEventRequest request) {
        validateEventRequest(request);

        String eventId = StringUtils.hasText(request.getEventId())
                ? request.getEventId().trim()
                : VppAuditHelper.nextEventId();
        ensureEventIdUnique(eventId, null);

        VppDrEvent event = new VppDrEvent();
        event.setEventId(eventId);
        event.setEventName(StringUtils.hasText(request.getEventName()) ? request.getEventName() : eventId);
        event.setResponseType(request.getResponseType());
        event.setEventType(request.getEventType());
        event.setStartTime(request.getStartTime());
        event.setEndTime(request.getEndTime());
        event.setDeclareDeadline(request.getDeclareDeadline());
        event.setInviteScope(request.getInviteScope());
        event.setTargetCapacityKw(request.getTargetCapacityKw());
        event.setClearedCapacityKw(request.getClearedCapacityKw());
        event.setRemark(request.getRemark());
        event.setAttachmentUrl(request.getAttachmentUrl());
        event.setAttachmentName(request.getAttachmentName());
        event.setSubsidyPrice(request.getSubsidyPrice());
        event.setFloatingCoefficient(request.getFloatingCoefficient());
        event.setEventStatus(EVENT_STATUS_PENDING);
        VppAuditHelper.fillCreate(event);
        eventMapper.insert(event);
        return event;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateEvent(Long id, DrEventRequest request) {
        VppDrEvent event = requireEvent(id);
        if (event.getEventStatus() != EVENT_STATUS_PENDING) {
            throw new BusinessException("仅待参与状态的事件可修改");
        }
        if (hasEventRelatedData(id)) {
            throw new BusinessException("事件已有关联参与记录，不可修改");
        }

        validateEventRequest(request);

        event.setEventName(StringUtils.hasText(request.getEventName()) ? request.getEventName() : event.getEventId());
        event.setResponseType(request.getResponseType());
        event.setEventType(request.getEventType());
        event.setStartTime(request.getStartTime());
        event.setEndTime(request.getEndTime());
        event.setDeclareDeadline(request.getDeclareDeadline());
        event.setInviteScope(request.getInviteScope());
        event.setTargetCapacityKw(request.getTargetCapacityKw());
        event.setClearedCapacityKw(request.getClearedCapacityKw());
        event.setRemark(request.getRemark());
        event.setAttachmentUrl(request.getAttachmentUrl());
        event.setAttachmentName(request.getAttachmentName());
        event.setSubsidyPrice(request.getSubsidyPrice());
        event.setFloatingCoefficient(request.getFloatingCoefficient());
        VppAuditHelper.fillUpdate(event);
        eventMapper.updateById(event);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateEventStatus(DrEventStatusRequest request) {
        if (request == null || request.getId() == null) {
            throw new BusinessException("事件ID不能为空");
        }
        if (request.getEventStatus() == null) {
            throw new BusinessException("事件状态不能为空");
        }
        if (request.getEventStatus() != EVENT_STATUS_DECLARED) {
            throw new BusinessException("仅允许将事件从待参与改为已申报");
        }

        VppDrEvent event = requireEvent(request.getId());
        Integer tenantId = SecurityUtils.getTenantId();
        if (tenantId != null && event.getTenantId() != null && !tenantId.equals(event.getTenantId())) {
            throw new BusinessException("无权操作其他租户的事件");
        }
        if (event.getEventStatus() == null || event.getEventStatus() != EVENT_STATUS_PENDING) {
            throw new BusinessException("仅待参与状态的事件可改为已申报");
        }

        event.setEventStatus(EVENT_STATUS_DECLARED);
        VppAuditHelper.fillUpdate(event);
        eventMapper.updateById(event);
        for (VppDrInvitation invitation : loadEventInvitations(event.getId())) {
            invitation.setResponseStatus(EVENT_STATUS_DECLARED);
            if (invitation.getTenantId() == null) {
                invitation.setTenantId(event.getTenantId() != null ? event.getTenantId() : tenantId);
            }
            VppAuditHelper.fillUpdate(invitation);
            invitationMapper.updateById(invitation);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteEvent(Long id) {
        VppDrEvent event = requireEvent(id);
        if (event.getEventStatus() != EVENT_STATUS_PENDING && event.getEventStatus() != EVENT_STATUS_CANCELLED) {
            throw new BusinessException("仅待参与或已取消状态的事件可删除");
        }
        if (hasEventRelatedData(id)) {
            throw new BusinessException("事件已有关联业务数据，不可删除");
        }

        VppAuditHelper.fillSoftDelete(event);
        eventMapper.updateById(event);
    }

    @Override
    public Object assessCapability(Long eventId) {
        VppDrEvent event = requireEvent(eventId);

        LambdaQueryWrapper<VppResourcePoint> resourceWrapper = new LambdaQueryWrapper<VppResourcePoint>()
                .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .eq(VppResourcePoint::getIsControl, 1);

        List<VppResourcePoint> resources = resourcePointMapper.selectList(resourceWrapper);
        Map<Long, com.usky.vpp.domain.VppSite> siteMap = siteResourceHelper.loadSiteMap(resources);

        BigDecimal totalUpCapacity = BigDecimal.ZERO;
        BigDecimal totalDownCapacity = BigDecimal.ZERO;
        int avgResponseTime = 120;
        BigDecimal avgRampSpeed = BigDecimal.valueOf(50);

        List<Map<String, Object>> resourceList = new ArrayList<>();
        for (VppResourcePoint resource : resources) {
            com.usky.vpp.domain.VppSite site = siteMap.get(resource.getSiteId());
            Map<String, Object> item = new HashMap<>();
            item.put("resourceId", resource.getId());
            item.put("resourceName", resource.getResourceName());
            item.put("upCapacityKw", resource.getMaxUpKw() != null ? resource.getMaxUpKw() : BigDecimal.ZERO);
            item.put("downCapacityKw", resource.getMinDownKw() != null ? resource.getMinDownKw() : BigDecimal.ZERO);
            item.put("responsePriority", site != null ? site.getResponsePriority() : null);
            item.put("available", resource.getIsControl() != null && resource.getIsControl() == 1);
            resourceList.add(item);

            BigDecimal up = resource.getMaxUpKw() != null ? resource.getMaxUpKw() : BigDecimal.ZERO;
            BigDecimal down = resource.getMinDownKw() != null ? resource.getMinDownKw() : BigDecimal.ZERO;
            totalUpCapacity = totalUpCapacity.add(up);
            totalDownCapacity = totalDownCapacity.add(down);
        }

        Map<String, Object> result = new HashMap<>();
        result.put("eventId", event.getEventId());
        result.put("targetCapacityKw", event.getTargetCapacityKw());
        result.put("totalUpCapacityKw", totalUpCapacity);
        result.put("totalDownCapacityKw", totalDownCapacity);
        result.put("avgResponseTimeSec", avgResponseTime);
        result.put("avgRampSpeedKwMin", avgRampSpeed);
        result.put("sufficient", event.getTargetCapacityKw() == null
                || totalUpCapacity.compareTo(event.getTargetCapacityKw()) >= 0);
        result.put("resources", resourceList);
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void participate(Long eventId, DrParticipateRequest request) {
        VppDrEvent event = requireEvent(eventId);

//        if (event.getEventStatus() != EVENT_STATUS_PENDING) {
//            throw new BusinessException("当前状态不允许参与");
//        }

        if (request == null || request.getParticipate() == null) {
            throw new BusinessException("参与状态不能为空");
        }

//        List<VppDrParticipation> existingParticipations = participationMapper.selectList(
//                new LambdaQueryWrapper<VppDrParticipation>()
//                        .eq(VppDrParticipation::getEventId, eventId)
//                        .eq(VppDrParticipation::getParticipateStatus, PARTICIPATE_STATUS_ACCEPT)
//                        .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
//        );
//
//        if (!existingParticipations.isEmpty()) {
//            throw new BusinessException("已参与该事件");
//        }

        if (Boolean.TRUE.equals(request.getParticipate())) {
            if (request.getResources() == null || request.getResources().isEmpty()) {
                throw new BusinessException("必须选择参与资源");
            }

            List<VppResourcePoint> selectedResources = request.getResources().stream()
                    .map(r -> r.getResourceId() == null ? null : resourcePointMapper.selectById(r.getResourceId()))
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
            Map<Long, com.usky.vpp.domain.VppSite> siteMap = siteResourceHelper.loadSiteMap(selectedResources);

            List<VppDrInvitation> invitations = invitationMapper.selectList(
                    new LambdaQueryWrapper<VppDrInvitation>()
                            .eq(VppDrInvitation::getDrEventId, eventId)
                            .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED));
            Map<Long, VppDrInvitation> invitationByCustomerId = invitations.stream()
                    .filter(inv -> inv.getCustomerId() != null)
                    .collect(Collectors.toMap(VppDrInvitation::getCustomerId, inv -> inv, (a, b) -> a));
            Map<Long, VppDrInvitation> invitationBySiteId = invitations.stream()
                    .filter(inv -> inv.getSiteId() != null)
                    .collect(Collectors.toMap(VppDrInvitation::getSiteId, inv -> inv, (a, b) -> a));

            Map<Long, VppDrInvitation> matchedInvitations = new LinkedHashMap<>();
            Map<Long, BigDecimal> declaredByResourceId = new HashMap<>();
            for (DrParticipateRequest.DrResourceDeclare resource : request.getResources()) {
                if (resource.getResourceId() == null) {
                    throw new BusinessException("资源ID不能为空");
                }
                VppResourcePoint rp = resourcePointMapper.selectById(resource.getResourceId());
                if (rp == null || VppAuditHelper.isDeleted(rp.getDeleteFlag())) {
                    throw new BusinessException("资源点不存在");
                }
                if (rp.getIsControl() == null || rp.getIsControl() != 1) {
                    throw new BusinessException("资源点不可控，无法参与");
                }
                Long customerId = siteResourceHelper.resolveCustomerId(rp, siteMap);
                if (customerId == null) {
                    throw new BusinessException("资源点未关联客户");
                }
                VppDrInvitation invitation = resolveInvitationForResource(
                        customerId, rp.getSiteId(), invitationByCustomerId, invitationBySiteId);
                if (invitation == null) {
                    throw new BusinessException("资源点未关联邀约，无法申报");
                }
                BigDecimal declaredCapacityKw = invitation.getDeclaredCapacityKw();
                if (declaredCapacityKw == null || declaredCapacityKw.compareTo(BigDecimal.ZERO) <= 0) {
                    throw new BusinessException("邀约申报出清容量必须大于0");
                }
                declaredByResourceId.put(resource.getResourceId(), declaredCapacityKw);
                matchedInvitations.put(invitation.getId(), invitation);
            }


            List<VppDrParticipation> savedParticipations = new ArrayList<>();

//            for (DrParticipateRequest.DrResourceDeclare resource : request.getResources()) {
//                VppResourcePoint rp = resourcePointMapper.selectById(resource.getResourceId());
//                VppDrParticipation participation = new VppDrParticipation();
//                participation.setEventId(eventId);
//                participation.setCustomerId(siteResourceHelper.resolveCustomerId(rp, siteMap));
//                participation.setResourceId(resource.getResourceId());
//                participation.setParticipateStatus(PARTICIPATE_STATUS_ACCEPT);
//                participation.setDeclaredCapacityKw(declaredByResourceId.get(resource.getResourceId()));
//                participation.setDeclaredAt(LocalDateTime.now());
//                VppDrParticipationHelper.refreshCompletionRate(participation);
//                VppAuditHelper.fillCreate(participation);
//                if (participation.getTenantId() == null && event.getTenantId() != null) {
//                    participation.setTenantId(event.getTenantId());
//                }
//                participationMapper.insert(participation);
//
//                savedParticipations.add(participation);
//
//            }

            event.setEventStatus(EVENT_STATUS_DECLARED);
            VppAuditHelper.fillUpdate(event);
            eventMapper.updateById(event);
            syncParticipationToUn(eventId, request.getParticipate(), savedParticipations);
            return;

        } else {
            event.setEventStatus(EVENT_STATUS_CANCELLED);
            VppAuditHelper.fillUpdate(event);
            eventMapper.updateById(event);
            syncInvitationStatus(event, false, loadEventInvitations(eventId), Collections.emptyList());
        }

        syncParticipationToUn(eventId, request.getParticipate(), null);
    }

    private void syncParticipationToUn(Long eventId, Boolean participate, List<VppDrParticipation> participations) {
        try {
            unIntegrationService.submitParticipation(eventId, Boolean.TRUE.equals(participate), participations);
        } catch (Exception ex) {
            log.warn("同步 UN 参与申报失败 eventId={}: {}", eventId, ex.getMessage());
        }
    }

    private List<VppDrInvitation> loadEventInvitations(Long eventId) {
        return invitationMapper.selectList(new LambdaQueryWrapper<VppDrInvitation>()
                .eq(VppDrInvitation::getDrEventId, eventId)
                .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED));
    }

    private void syncInvitationStatus(VppDrEvent event, boolean participate,
                                      Collection<VppDrInvitation> invitations,
                                      List<VppDrParticipation> createdParticipations) {
        if (invitations == null || invitations.isEmpty()) {
            return;
        }
        Map<Long, Long> participationIdByCustomer = createdParticipations == null
                ? Collections.emptyMap()
                : createdParticipations.stream()
                .filter(p -> p.getCustomerId() != null && p.getId() != null)
                .collect(Collectors.toMap(VppDrParticipation::getCustomerId, VppDrParticipation::getId, (a, b) -> a));
        for (VppDrInvitation invitation : invitations) {
            if (invitation == null) {
                continue;
            }
            if (participate) {
                invitation.setReplyStatus(REPLY_ACCEPT);
                invitation.setResponseStatus(EVENT_STATUS_DECLARED);
                if (invitation.getParticipationId() == null && invitation.getCustomerId() != null) {
                    Long participationId = participationIdByCustomer.get(invitation.getCustomerId());
                    if (participationId != null) {
                        invitation.setParticipationId(participationId);
                    }
                }
            } else {
                invitation.setReplyStatus(REPLY_REJECT);
                invitation.setResponseStatus(EVENT_STATUS_CANCELLED);
            }
            if (invitation.getTenantId() == null) {
                invitation.setTenantId(event.getTenantId() != null ? event.getTenantId() : SecurityUtils.getTenantId());
            }
            VppAuditHelper.fillUpdate(invitation);
            invitationMapper.updateById(invitation);
        }
    }

    private VppDrInvitation resolveInvitationForResource(Long customerId, Long siteId,
                                                         Map<Long, VppDrInvitation> invitationByCustomerId,
                                                         Map<Long, VppDrInvitation> invitationBySiteId) {
        if (customerId != null) {
            VppDrInvitation invitation = invitationByCustomerId.get(customerId);
            if (invitation != null) {
                return invitation;
            }
        }
        if (siteId != null) {
            return invitationBySiteId.get(siteId);
        }
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void clearing(Long eventId, DrClearingRequest request) {
        VppDrEvent event = requireEvent(eventId);

        if (event.getEventStatus() != EVENT_STATUS_DECLARED) {
            throw new BusinessException("当前状态不允许出清分拆");
        }

        if (request == null || request.getTotalClearedCapacityKw() == null) {
            throw new BusinessException("出清总容量不能为空");
        }

        if (request.getResources() == null || request.getResources().isEmpty()) {
            throw new BusinessException("资源分拆列表不能为空");
        }

        long existingExecCount = executionMapper.selectCount(
                new LambdaQueryWrapper<VppDrExecution>()
                        .eq(VppDrExecution::getEventId, eventId)
                        .eq(VppDrExecution::getDeleteFlag, VppAuditHelper.NOT_DELETED)
        );
        if (existingExecCount > 0) {
            throw new BusinessException("已出清，请勿重复操作");
        }

        BigDecimal totalCleared = BigDecimal.ZERO;
        for (DrClearingRequest.DrResourceClearing resource : request.getResources()) {
            if (resource.getResourceId() == null) {
                throw new BusinessException("资源ID不能为空");
            }
            if (resource.getClearedCapacityKw() == null || resource.getClearedCapacityKw().compareTo(BigDecimal.ZERO) <= 0) {
                throw new BusinessException("出清容量必须大于0");
            }
            totalCleared = totalCleared.add(resource.getClearedCapacityKw());
        }

        if (totalCleared.compareTo(request.getTotalClearedCapacityKw()) != 0) {
            throw new BusinessException("分拆容量之和与总出清容量不一致");
        }

        for (DrClearingRequest.DrResourceClearing resource : request.getResources()) {
            LambdaQueryWrapper<VppDrParticipation> wrapper = new LambdaQueryWrapper<VppDrParticipation>()
                    .eq(VppDrParticipation::getEventId, eventId)
                    .eq(VppDrParticipation::getResourceId, resource.getResourceId())
                    .eq(VppDrParticipation::getParticipateStatus, PARTICIPATE_STATUS_ACCEPT)
                    .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED);

            VppDrParticipation participation = participationMapper.selectOne(wrapper);
            if (participation == null) {
                throw new BusinessException("资源未参与申报，无法出清: " + resource.getResourceId());
            }
            if (participation.getDeclaredCapacityKw() != null
                    && resource.getClearedCapacityKw().compareTo(participation.getDeclaredCapacityKw()) > 0) {
                throw new BusinessException("出清容量不能超过申报出清容量");
            }
            participation.setClearedCapacityKw(resource.getClearedCapacityKw());
            VppDrParticipationHelper.refreshCompletionRate(participation);
            VppAuditHelper.fillUpdate(participation);
            participationMapper.updateById(participation);
        }

        event.setClearedCapacityKw(request.getTotalClearedCapacityKw());
        event.setEventStatus(EVENT_STATUS_EXECUTING);
        VppAuditHelper.fillUpdate(event);
        eventMapper.updateById(event);

        for (DrClearingRequest.DrResourceClearing resource : request.getResources()) {
            VppDrExecution execution = new VppDrExecution();
            execution.setEventId(eventId);
            execution.setResourceId(resource.getResourceId());
            execution.setTargetKw(resource.getClearedCapacityKw());
            execution.setActualKw(BigDecimal.ZERO);
            execution.setExecuteStatus(EXECUTE_STATUS_RUNNING);
            execution.setStartedAt(LocalDateTime.now());
            VppAuditHelper.fillCreate(execution);
            executionMapper.insert(execution);
        }

        syncClearingToUn(eventId);

        // ========== 出清完成后，自动生成补贴金额预测 ==========
        try {
            subsidyPredictionService.generateByEventId(eventId);
        } catch (Exception ex) {
            log.warn("补贴预测生成失败 eventId={}: {}", eventId, ex.getMessage());
        }
    }

    private void syncClearingToUn(Long eventId) {
        try {
            unIntegrationService.submitClearing(eventId);
        } catch (Exception ex) {
            log.warn("同步 UN 出清分拆失败 eventId={}: {}", eventId, ex.getMessage());
        }
    }

    @Override
    public Object monitorExecution(Long eventId) {
        VppDrEvent event = requireEvent(eventId);

        List<VppDrExecution> executions = executionMapper.selectList(
                new LambdaQueryWrapper<VppDrExecution>()
                        .eq(VppDrExecution::getEventId, eventId)
                        .eq(VppDrExecution::getDeleteFlag, VppAuditHelper.NOT_DELETED)
        );

        BigDecimal totalTarget = BigDecimal.ZERO;
        BigDecimal totalActual = BigDecimal.ZERO;

        List<Map<String, Object>> resources = new ArrayList<>();
        for (VppDrExecution exe : executions) {
            VppResourcePoint rp = resourcePointMapper.selectById(exe.getResourceId());

            Map<String, Object> item = new HashMap<>();
            item.put("resourceId", exe.getResourceId());
            item.put("resourceName", rp != null ? rp.getResourceName() : "");
            item.put("targetKw", exe.getTargetKw());
            item.put("actualKw", exe.getActualKw());
            item.put("executeStatus", exe.getExecuteStatus());
            resources.add(item);

            totalTarget = totalTarget.add(exe.getTargetKw() != null ? exe.getTargetKw() : BigDecimal.ZERO);
            totalActual = totalActual.add(exe.getActualKw() != null ? exe.getActualKw() : BigDecimal.ZERO);
        }

        int remainingMinutes = 0;
        if (event.getEndTime() != null) {
            remainingMinutes = (int) java.time.Duration.between(LocalDateTime.now(), event.getEndTime()).toMinutes();
        }

        Map<String, Object> result = new HashMap<>();
        result.put("eventId", event.getEventId());
        result.put("targetCapacityKw", totalTarget.compareTo(BigDecimal.ZERO) > 0 ? totalTarget : event.getClearedCapacityKw());
        result.put("actualCapacityKw", totalActual);
        result.put("progressPercent", totalTarget.compareTo(BigDecimal.ZERO) > 0 ?
                totalActual.multiply(BigDecimal.valueOf(100)).divide(totalTarget, 2, RoundingMode.HALF_UP) : BigDecimal.ZERO);
        result.put("remainingMinutes", remainingMinutes > 0 ? remainingMinutes : 0);
        result.put("resources", resources);
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void intervene(Long eventId, DrInterveneRequest request) {
        VppDrEvent event = requireEvent(eventId);

        if (event.getEventStatus() != EVENT_STATUS_EXECUTING) {
            throw new BusinessException("仅执行中的事件允许干预");
        }

        if (request == null || !StringUtils.hasText(request.getAction())) {
            throw new BusinessException("干预动作不能为空");
        }

        String action = request.getAction();
        List<VppDrExecution> executions = executionMapper.selectList(
                new LambdaQueryWrapper<VppDrExecution>()
                        .eq(VppDrExecution::getEventId, eventId)
                        .eq(VppDrExecution::getDeleteFlag, VppAuditHelper.NOT_DELETED)
        );
        if (executions.isEmpty()) {
            throw new BusinessException("暂无执行明细，无法干预");
        }

        if ("ADJUST".equals(action)) {
            if (request.getStrategyId() == null) {
                throw new BusinessException("切换策略时必须指定策略ID");
            }
            VppDrStrategy strategy = strategyMapper.selectById(request.getStrategyId());
            if (strategy == null || VppAuditHelper.isDeleted(strategy.getDeleteFlag())) {
                throw new BusinessException("策略不存在");
            }
            if (strategy.getIsEnabled() != null && strategy.getIsEnabled() == 0) {
                throw new BusinessException("策略未启用");
            }
            applyStrategyTargets(event, executions, request.getStrategyId());
        } else if ("PAUSE".equals(action)) {
            for (VppDrExecution execution : executions) {
                if (execution.getExecuteStatus() == EXECUTE_STATUS_RUNNING) {
                    execution.setExecuteStatus(EXECUTE_STATUS_PENDING);
                    VppAuditHelper.fillUpdate(execution);
                    executionMapper.updateById(execution);
                }
            }
        } else if ("RESUME".equals(action)) {
            for (VppDrExecution execution : executions) {
                if (execution.getExecuteStatus() == EXECUTE_STATUS_PENDING) {
                    execution.setExecuteStatus(EXECUTE_STATUS_RUNNING);
                    if (execution.getStartedAt() == null) {
                        execution.setStartedAt(LocalDateTime.now());
                    }
                    VppAuditHelper.fillUpdate(execution);
                    executionMapper.updateById(execution);
                }
            }
        } else {
            throw new BusinessException("无效的干预动作");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void completeEvent(Long eventId) {
        VppDrEvent event = requireEvent(eventId);
        if (event.getEventStatus() != EVENT_STATUS_EXECUTING) {
            throw new BusinessException("仅执行中的事件可结束");
        }

        List<VppDrExecution> executions = executionMapper.selectList(
                new LambdaQueryWrapper<VppDrExecution>()
                        .eq(VppDrExecution::getEventId, eventId)
                        .eq(VppDrExecution::getDeleteFlag, VppAuditHelper.NOT_DELETED)
        );
        if (executions.isEmpty()) {
            throw new BusinessException("暂无执行明细，无法结束事件");
        }

        BigDecimal totalActual = BigDecimal.ZERO;
        LocalDateTime now = LocalDateTime.now();
        for (VppDrExecution execution : executions) {
            BigDecimal actual = execution.getActualKw();
            if (actual == null || actual.compareTo(BigDecimal.ZERO) == 0) {
                actual = execution.getTargetKw() != null ? execution.getTargetKw() : BigDecimal.ZERO;
                execution.setActualKw(actual);
            }
            totalActual = totalActual.add(actual);
            execution.setExecuteStatus(EXECUTE_STATUS_FINISHED);
            execution.setFinishedAt(now);
            VppAuditHelper.fillUpdate(execution);
            executionMapper.updateById(execution);
        }

        event.setEventStatus(EVENT_STATUS_ENDED);
        VppAuditHelper.fillUpdate(event);
        eventMapper.updateById(event);

        // ========== 事件结束后，自动生成补贴金额预测 ==========
        try {
            subsidyPredictionService.generateByEventId(eventId);
        } catch (Exception ex) {
            log.warn("补贴预测生成失败 eventId={}: {}", eventId, ex.getMessage());
        }

        VppDrEvaluation evaluation = evaluationMapper.selectOne(
                new LambdaQueryWrapper<VppDrEvaluation>()
                        .eq(VppDrEvaluation::getEventId, eventId)
                        .eq(VppDrEvaluation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
        );
        if (evaluation == null) {
            evaluation = new VppDrEvaluation();
            evaluation.setEventId(eventId);
            evaluation.setActualCapacityKw(totalActual);
            if (event.getStartTime() != null && event.getEndTime() != null) {
                evaluation.setResponseDurationMin(
                        (int) java.time.Duration.between(event.getStartTime(), event.getEndTime()).toMinutes());
            }
            if (event.getSubsidyPrice() != null) {
                evaluation.setSubsidyAmount(totalActual.multiply(event.getSubsidyPrice()));
            }
            BigDecimal cleared = event.getClearedCapacityKw();
            if (cleared != null && cleared.compareTo(BigDecimal.ZERO) > 0) {
                evaluation.setQualifiedRate(totalActual.multiply(BigDecimal.valueOf(100))
                        .divide(cleared, 2, RoundingMode.HALF_UP));
            }
            evaluation.setEvaluatedAt(now);
            VppAuditHelper.fillCreate(evaluation);
            evaluationMapper.insert(evaluation);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void acknowledgeClearing(Long eventId) {
        VppDrEvent event = requireEvent(eventId);
        if (event.getEventStatus() != EVENT_STATUS_DECLARED) {
            throw new BusinessException("当前状态不允许确认出清公示");
        }
        unIntegrationService.acknowledgeClearing(eventId);
    }

    @Override
    public VppDrEvaluation getEvaluation(Long eventId) {
        VppDrEvent event = requireEvent(eventId);

        if (event.getEventStatus() != EVENT_STATUS_ENDED) {
            throw new BusinessException("事件未结束，暂无评估结果");
        }

        LambdaQueryWrapper<VppDrEvaluation> wrapper = new LambdaQueryWrapper<VppDrEvaluation>()
                .eq(VppDrEvaluation::getEventId, eventId)
                .eq(VppDrEvaluation::getDeleteFlag, VppAuditHelper.NOT_DELETED);

        VppDrEvaluation evaluation = evaluationMapper.selectOne(wrapper);
        if (evaluation == null) {
            throw new BusinessException("评估结果不存在");
        }
        return evaluation;
    }

    @Override
    public CommonPage<VppDrStrategy> pageStrategy(Map<String, Object> params) {
        Page<VppDrStrategy> page = VppPageHelper.of(params);
        LambdaQueryWrapper<VppDrStrategy> wrapper = new LambdaQueryWrapper<VppDrStrategy>()
                .eq(VppDrStrategy::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .orderByDesc(VppDrStrategy::getCreateTime);

        if (params != null) {
            if (params.get("responseType") != null) {
                wrapper.eq(VppDrStrategy::getResponseType, Integer.parseInt(params.get("responseType").toString()));
            }
            if (params.get("isEnabled") != null) {
                wrapper.eq(VppDrStrategy::getIsEnabled, Integer.parseInt(params.get("isEnabled").toString()));
            }
            if (params.get("strategyName") != null) {
                wrapper.like(VppDrStrategy::getStrategyName, params.get("strategyName").toString());
            }
        }

        Page<VppDrStrategy> result = strategyMapper.selectPage(page, wrapper);
        return new CommonPage<>(result.getRecords(), result.getTotal(), result.getCurrent(), result.getSize());
    }

    @Override
    public DrStrategyVO getStrategy(Long id) {
        VppDrStrategy strategy = strategyMapper.selectById(id);
        if (strategy == null || VppAuditHelper.isDeleted(strategy.getDeleteFlag())) {
            throw new BusinessException("策略不存在");
        }
        return toStrategyVo(strategy);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public VppDrStrategy createStrategy(DrStrategyRequest request) {
        validateStrategyRequest(request);

        VppDrStrategy strategy = new VppDrStrategy();
        strategy.setStrategyCode(request.getStrategyCode());
        strategy.setStrategyName(request.getStrategyName());
        strategy.setResponseType(request.getResponseType());
        strategy.setAdjustStepKw(request.getAdjustStepKw());
        strategy.setExecuteMode(request.getExecuteMode() != null ? request.getExecuteMode() : 1);
        strategy.setStrategyConfig(request.getStrategyConfig());
        strategy.setIsDefault(request.getIsDefault() != null ? request.getIsDefault() : 0);
        strategy.setIsEnabled(request.getIsEnabled() != null ? request.getIsEnabled() : 1);
        VppAuditHelper.fillCreate(strategy);
        strategyMapper.insert(strategy);

        if (request.getIsDefault() != null && request.getIsDefault() == 1) {
            LambdaQueryWrapper<VppDrStrategy> wrapper = new LambdaQueryWrapper<VppDrStrategy>()
                    .ne(VppDrStrategy::getId, strategy.getId())
                    .eq(VppDrStrategy::getIsDefault, 1)
                    .eq(VppDrStrategy::getDeleteFlag, VppAuditHelper.NOT_DELETED);

            List<VppDrStrategy> defaults = strategyMapper.selectList(wrapper);
            for (VppDrStrategy def : defaults) {
                def.setIsDefault(0);
                VppAuditHelper.fillUpdate(def);
                strategyMapper.updateById(def);
            }
        }

        if (request.getResources() != null) {
            for (DrStrategyRequest.DrStrategyResource resource : request.getResources()) {
                VppDrStrategyResource sr = new VppDrStrategyResource();
                sr.setStrategyId(strategy.getId());
                sr.setResourceId(resource.getResourceId());
                sr.setPriority(resource.getPriority() != null ? resource.getPriority() : 0);
                sr.setMaxAdjustKw(resource.getMaxAdjustKw());
                strategyResourceMapper.insert(sr);
            }
        }

        return strategy;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStrategy(Long id, DrStrategyRequest request) {
        VppDrStrategy strategy = strategyMapper.selectById(id);
        if (strategy == null || VppAuditHelper.isDeleted(strategy.getDeleteFlag())) {
            throw new BusinessException("策略不存在");
        }

        validateStrategyRequest(request);

        strategy.setStrategyCode(request.getStrategyCode());
        strategy.setStrategyName(request.getStrategyName());
        strategy.setResponseType(request.getResponseType());
        strategy.setAdjustStepKw(request.getAdjustStepKw());
        if (request.getExecuteMode() != null) {
            strategy.setExecuteMode(request.getExecuteMode());
        }
        strategy.setStrategyConfig(request.getStrategyConfig());
        if (request.getIsDefault() != null) {
            strategy.setIsDefault(request.getIsDefault());
        }
        if (request.getIsEnabled() != null) {
            strategy.setIsEnabled(request.getIsEnabled());
        }

        if (request.getIsDefault() != null && request.getIsDefault() == 1) {
            LambdaQueryWrapper<VppDrStrategy> wrapper = new LambdaQueryWrapper<VppDrStrategy>()
                    .ne(VppDrStrategy::getId, id)
                    .eq(VppDrStrategy::getIsDefault, 1)
                    .eq(VppDrStrategy::getDeleteFlag, VppAuditHelper.NOT_DELETED);

            List<VppDrStrategy> defaults = strategyMapper.selectList(wrapper);
            for (VppDrStrategy def : defaults) {
                def.setIsDefault(0);
                VppAuditHelper.fillUpdate(def);
                strategyMapper.updateById(def);
            }
        }

        VppAuditHelper.fillUpdate(strategy);
        strategyMapper.updateById(strategy);

        strategyResourceMapper.delete(new LambdaQueryWrapper<VppDrStrategyResource>()
                .eq(VppDrStrategyResource::getStrategyId, id));

        if (request.getResources() != null) {
            for (DrStrategyRequest.DrStrategyResource resource : request.getResources()) {
                VppDrStrategyResource sr = new VppDrStrategyResource();
                sr.setStrategyId(id);
                sr.setResourceId(resource.getResourceId());
                sr.setPriority(resource.getPriority() != null ? resource.getPriority() : 0);
                sr.setMaxAdjustKw(resource.getMaxAdjustKw());
                strategyResourceMapper.insert(sr);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteStrategy(Long id) {
        VppDrStrategy strategy = strategyMapper.selectById(id);
        if (strategy == null || VppAuditHelper.isDeleted(strategy.getDeleteFlag())) {
            throw new BusinessException("策略不存在");
        }

        VppAuditHelper.fillSoftDelete(strategy);
        strategyMapper.updateById(strategy);

        strategyResourceMapper.delete(new LambdaQueryWrapper<VppDrStrategyResource>()
                .eq(VppDrStrategyResource::getStrategyId, id));
    }

    private void validateEventRequest(DrEventRequest request) {
        if (request == null) {
            throw new BusinessException("请求不能为空");
        }
        if (request.getResponseType() == null) {
            throw new BusinessException("响应类型不能为空");
        }
        if (request.getResponseType() < 1 || request.getResponseType() > 3) {
            throw new BusinessException("响应类型无效，取值 1日前 2日内 3秒级");
        }
        if (request.getEventType() != null && (request.getEventType() < 1 || request.getEventType() > 2)) {
            throw new BusinessException("事件类型无效，取值 1削峰 2填谷");
        }
        if (request.getStartTime() == null) {
            throw new BusinessException("响应开始时间不能为空");
        }
        if (request.getEndTime() == null) {
            throw new BusinessException("响应结束时间不能为空");
        }
        if (request.getEndTime().isBefore(request.getStartTime())) {
            throw new BusinessException("响应结束时间不能早于开始时间");
        }
//        if (request.getTargetCapacityKw() == null || request.getTargetCapacityKw().compareTo(BigDecimal.ZERO) <= 0) {
//            throw new BusinessException("邀约规模必须大于0");
//        }
        if (request.getClearedCapacityKw() != null && request.getClearedCapacityKw().compareTo(BigDecimal.ZERO) < 0) {
            throw new BusinessException("出清规模不能为负数");
        }
        if (request.getDeclareDeadline() != null && request.getStartTime() != null
                && request.getDeclareDeadline().isAfter(request.getStartTime())) {
            throw new BusinessException("申报截止时间不能晚于响应开始时间");
        }
        if (request.getSubsidyPrice() != null && request.getSubsidyPrice().compareTo(BigDecimal.ZERO) < 0) {
            throw new BusinessException("补贴标准不能为负数");
        }
        if (request.getFloatingCoefficient() == null) {
            throw new BusinessException("下浮系数不能为空");
        }
    }

    private void ensureEventIdUnique(String eventId, Long excludeId) {
        LambdaQueryWrapper<VppDrEvent> wrapper = new LambdaQueryWrapper<VppDrEvent>()
                .eq(VppDrEvent::getEventId, eventId)
                .eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED);
        if (excludeId != null) {
            wrapper.ne(VppDrEvent::getId, excludeId);
        }
        if (eventMapper.selectCount(wrapper) > 0) {
            throw new BusinessException("事件ID已存在");
        }
    }

    private boolean hasEventRelatedData(Long eventId) {
        long participationCount = participationMapper.selectCount(
                new LambdaQueryWrapper<VppDrParticipation>()
                        .eq(VppDrParticipation::getEventId, eventId)
                        .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED));
        if (participationCount > 0) {
            return true;
        }
        long executionCount = executionMapper.selectCount(
                new LambdaQueryWrapper<VppDrExecution>()
                        .eq(VppDrExecution::getEventId, eventId)
                        .eq(VppDrExecution::getDeleteFlag, VppAuditHelper.NOT_DELETED));
        if (executionCount > 0) {
            return true;
        }
        return evaluationMapper.selectCount(
                new LambdaQueryWrapper<VppDrEvaluation>()
                        .eq(VppDrEvaluation::getEventId, eventId)
                        .eq(VppDrEvaluation::getDeleteFlag, VppAuditHelper.NOT_DELETED)) > 0;
    }

    private DrEventDetailVO toEventDetailVo(VppDrEvent event) {
        DrEventDetailVO vo = new DrEventDetailVO();
        vo.setId(event.getId());
        vo.setEventId(event.getEventId());
        vo.setEventName(event.getEventName());
        vo.setResponseType(event.getResponseType());
        vo.setEventType(event.getEventType());
        vo.setStartTime(event.getStartTime());
        vo.setEndTime(event.getEndTime());
        vo.setDeclareDeadline(event.getDeclareDeadline());
        vo.setInviteScope(event.getInviteScope());
        vo.setTargetCapacityKw(event.getTargetCapacityKw());
        vo.setClearedCapacityKw(event.getClearedCapacityKw());
        vo.setRemark(event.getRemark());
        vo.setAttachmentUrl(event.getAttachmentUrl());
        vo.setAttachmentName(event.getAttachmentName());
        vo.setSubsidyPrice(event.getSubsidyPrice());
        vo.setFloatingCoefficient(event.getFloatingCoefficient());
        vo.setEventStatus(event.getEventStatus());
        vo.setParticipations(loadParticipationVos(event.getId()));
        return vo;
    }

    private List<DrParticipationVO> loadParticipationVos(Long eventId) {
        List<VppDrParticipation> participations = participationMapper.selectList(
                new LambdaQueryWrapper<VppDrParticipation>()
                        .eq(VppDrParticipation::getEventId, eventId)
                        .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                        .orderByAsc(VppDrParticipation::getDeclaredAt, VppDrParticipation::getId));
        if (participations.isEmpty()) {
            return Collections.emptyList();
        }

        Set<Long> customerIds = participations.stream()
                .map(VppDrParticipation::getCustomerId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Set<Long> resourceIds = participations.stream()
                .map(VppDrParticipation::getResourceId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        Map<Long, VppCustomer> customerMap = customerIds.isEmpty()
                ? Collections.emptyMap()
                : customerMapper.selectBatchIds(customerIds).stream()
                .filter(c -> !VppAuditHelper.isDeleted(c.getDeleteFlag()))
                .collect(Collectors.toMap(VppCustomer::getId, c -> c, (a, b) -> a));

        Map<Long, VppResourcePoint> resourceMap = resourceIds.isEmpty()
                ? Collections.emptyMap()
                : resourcePointMapper.selectBatchIds(resourceIds).stream()
                .filter(r -> !VppAuditHelper.isDeleted(r.getDeleteFlag()))
                .collect(Collectors.toMap(VppResourcePoint::getId, r -> r, (a, b) -> a));

        List<VppDrInvitation> invitations = invitationMapper.selectList(
                new LambdaQueryWrapper<VppDrInvitation>()
                        .eq(VppDrInvitation::getDrEventId, eventId)
                        .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED));
        Map<Long, VppDrInvitation> invitationByParticipationId = invitations.stream()
                .filter(inv -> inv.getParticipationId() != null)
                .collect(Collectors.toMap(VppDrInvitation::getParticipationId, inv -> inv, (a, b) -> a));
        Map<Long, VppDrInvitation> invitationByCustomerId = invitations.stream()
                .filter(inv -> inv.getCustomerId() != null)
                .collect(Collectors.toMap(VppDrInvitation::getCustomerId, inv -> inv, (a, b) -> a));

        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()
                .filter(s -> !VppAuditHelper.isDeleted(s.getDeleteFlag()))
                .collect(Collectors.toMap(VppSite::getId, VppSite::getSiteName, (a, b) -> a));

        return participations.stream().map(p -> {
            DrParticipationVO item = new DrParticipationVO();
            item.setId(p.getId());
            item.setCustomerId(p.getCustomerId());
            item.setResourceId(p.getResourceId());
            item.setParticipateStatus(p.getParticipateStatus());
            item.setDeclaredCapacityKw(p.getDeclaredCapacityKw());
            item.setClearedCapacityKw(p.getClearedCapacityKw());
            item.setCompletionRate(p.getCompletionRate() != null
                    ? p.getCompletionRate()
                    : VppDrParticipationHelper.calcCompletionRate(
                    p.getClearedCapacityKw(), p.getDeclaredCapacityKw()));
            item.setDeclaredAt(p.getDeclaredAt());
            VppCustomer customer = customerMap.get(p.getCustomerId());
            if (customer != null) {
                item.setCustomerName(customer.getCustomerName());
            }
            VppResourcePoint resource = resourceMap.get(p.getResourceId());
            if (resource != null) {
                item.setResourceName(resource.getResourceName());
            }
            fillInvitationFields(item, resolveInvitation(p, invitationByParticipationId, invitationByCustomerId));
            if (item.getSiteId() != null) {
                item.setSiteName(siteNameMap.get(item.getSiteId()));
            }
            return item;
        }).collect(Collectors.toList());
    }

    private VppDrInvitation resolveInvitation(VppDrParticipation participation,
                                              Map<Long, VppDrInvitation> invitationByParticipationId,
                                              Map<Long, VppDrInvitation> invitationByCustomerId) {
        VppDrInvitation invitation = invitationByParticipationId.get(participation.getId());
        if (invitation != null) {
            return invitation;
        }
        return invitationByCustomerId.get(participation.getCustomerId());
    }

    /**
     * 将邀约表字段完整映射到参与 VO，保留既有参与记录字段不被覆盖。
     */
    private void fillInvitationFields(DrParticipationVO item, VppDrInvitation invitation) {
        if (invitation == null) {
            return;
        }
        Long participationPk = item.getId();
        Long customerId = item.getCustomerId();
        BigDecimal declaredCapacityKw = item.getDeclaredCapacityKw();
        BeanUtils.copyProperties(invitation, item);
        item.setId(participationPk);
        item.setInvitationId(invitation.getId());
        item.setCustomerId(customerId);
        if (declaredCapacityKw != null) {
            item.setDeclaredCapacityKw(declaredCapacityKw);
        }
    }

    private DrStrategyVO toStrategyVo(VppDrStrategy strategy) {
        DrStrategyVO vo = new DrStrategyVO();
        vo.setId(strategy.getId());
        vo.setStrategyCode(strategy.getStrategyCode());
        vo.setStrategyName(strategy.getStrategyName());
        vo.setResponseType(strategy.getResponseType());
        vo.setAdjustStepKw(strategy.getAdjustStepKw());
        vo.setExecuteMode(strategy.getExecuteMode());
        vo.setStrategyConfig(strategy.getStrategyConfig());
        vo.setIsDefault(strategy.getIsDefault());
        vo.setIsEnabled(strategy.getIsEnabled());
        vo.setTenantId(strategy.getTenantId());
        vo.setCreateTime(strategy.getCreateTime());
        vo.setCreatedBy(strategy.getCreatedBy());

        List<VppDrStrategyResource> strategyResources = strategyResourceMapper.selectList(
                new LambdaQueryWrapper<VppDrStrategyResource>()
                        .eq(VppDrStrategyResource::getStrategyId, strategy.getId())
                        .orderByAsc(VppDrStrategyResource::getPriority)
        );
        if (strategyResources.isEmpty()) {
            vo.setResources(Collections.emptyList());
            return vo;
        }

        Set<Long> resourceIds = strategyResources.stream()
                .map(VppDrStrategyResource::getResourceId)
                .collect(Collectors.toSet());
        Map<Long, VppResourcePoint> resourceMap = resourcePointMapper.selectBatchIds(resourceIds).stream()
                .filter(rp -> !VppAuditHelper.isDeleted(rp.getDeleteFlag()))
                .collect(Collectors.toMap(VppResourcePoint::getId, rp -> rp, (a, b) -> a));

        List<DrStrategyVO.DrStrategyResourceVO> resources = strategyResources.stream().map(sr -> {
            DrStrategyVO.DrStrategyResourceVO item = new DrStrategyVO.DrStrategyResourceVO();
            item.setResourceId(sr.getResourceId());
            item.setPriority(sr.getPriority());
            item.setMaxAdjustKw(sr.getMaxAdjustKw());
            VppResourcePoint resourcePoint = resourceMap.get(sr.getResourceId());
            if (resourcePoint != null) {
                item.setResourceName(resourcePoint.getResourceName());
            }
            return item;
        }).collect(Collectors.toList());
        vo.setResources(resources);
        return vo;
    }

    private void validateStrategyRequest(DrStrategyRequest request) {
        if (request == null) {
            throw new BusinessException("请求不能为空");
        }
        if (!StringUtils.hasText(request.getStrategyCode())) {
            throw new BusinessException("策略编码不能为空");
        }
        if (!StringUtils.hasText(request.getStrategyName())) {
            throw new BusinessException("策略名称不能为空");
        }
        if (request.getResponseType() == null) {
            throw new BusinessException("响应类型不能为空");
        }
        if (request.getResources() != null) {
            for (DrStrategyRequest.DrStrategyResource resource : request.getResources()) {
                if (resource.getResourceId() == null) {
                    throw new BusinessException("策略资源ID不能为空");
                }
                VppResourcePoint rp = resourcePointMapper.selectById(resource.getResourceId());
                if (rp == null || VppAuditHelper.isDeleted(rp.getDeleteFlag())) {
                    throw new BusinessException("策略关联的资源点不存在");
                }
            }
        }
    }

    private void applyStrategyTargets(VppDrEvent event, List<VppDrExecution> executions, Long strategyId) {
        List<VppDrStrategyResource> strategyResources = strategyResourceMapper.selectList(
                new LambdaQueryWrapper<VppDrStrategyResource>()
                        .eq(VppDrStrategyResource::getStrategyId, strategyId)
                        .orderByAsc(VppDrStrategyResource::getPriority)
        );
        if (strategyResources.isEmpty()) {
            throw new BusinessException("策略未配置资源");
        }

        Map<Long, VppDrExecution> executionByResource = executions.stream()
                .collect(Collectors.toMap(VppDrExecution::getResourceId, e -> e, (a, b) -> a));

        BigDecimal totalMaxAdjust = strategyResources.stream()
                .map(sr -> sr.getMaxAdjustKw() != null ? sr.getMaxAdjustKw() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalTarget = event.getClearedCapacityKw() != null ? event.getClearedCapacityKw() : BigDecimal.ZERO;

        for (VppDrStrategyResource strategyResource : strategyResources) {
            VppDrExecution execution = executionByResource.get(strategyResource.getResourceId());
            if (execution == null) {
                continue;
            }
            BigDecimal maxAdjust = strategyResource.getMaxAdjustKw() != null ? strategyResource.getMaxAdjustKw() : BigDecimal.ZERO;
            BigDecimal newTarget = totalMaxAdjust.compareTo(BigDecimal.ZERO) > 0
                    ? totalTarget.multiply(maxAdjust).divide(totalMaxAdjust, 4, RoundingMode.HALF_UP)
                    : BigDecimal.ZERO;
            execution.setTargetKw(newTarget);
            execution.setExecuteStatus(EXECUTE_STATUS_RUNNING);
            if (execution.getStartedAt() == null) {
                execution.setStartedAt(LocalDateTime.now());
            }
            VppAuditHelper.fillUpdate(execution);
            executionMapper.updateById(execution);
        }
    }
}