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.VppDrEvent;
import com.usky.vpp.domain.VppDrInvitation;
import com.usky.vpp.domain.VppDrParticipation;
import com.usky.vpp.domain.VppResourcePoint;
import com.usky.vpp.mapper.VppDrEventMapper;
import com.usky.vpp.mapper.VppDrInvitationMapper;
import com.usky.vpp.mapper.VppDrParticipationMapper;
import com.usky.vpp.mapper.VppResourcePointMapper;
import com.usky.vpp.service.VppBaselineService;
import com.usky.vpp.service.VppDrMonitorService;
import com.usky.vpp.service.vo.BaselinePointVO;
import com.usky.vpp.service.vo.DrMonitorCurveVO;
import com.usky.vpp.service.vo.DrMonitorRecordVO;
import com.usky.vpp.service.vo.DrMonitorSummaryVO;
import com.usky.vpp.service.vo.SiteBaselineVO;
import com.usky.vpp.util.VppAuditHelper;
import com.usky.vpp.util.VppPageHelper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 需求响应监测（响应记录）
 * <p>统一口径：站点 → is_support_peak=1 资源点 → 按资源点平均。</p>
 */
@Service
public class VppDrMonitorServiceImpl implements VppDrMonitorService {

    private static final int EVENT_STATUS_EXECUTING = 2;
    private static final int EVENT_STATUS_ENDED = 3;
    private static final int SUPPORT_PEAK = 1;
    private static final int PARTICIPATE_ACCEPT = 1;
    private static final int SCALE = 4;
    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter DATE_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final BigDecimal ONE = BigDecimal.ONE;
    private static final BigDecimal HUNDRED = new BigDecimal("100");

    @Autowired
    private VppDrEventMapper eventMapper;
    @Autowired
    private VppDrInvitationMapper invitationMapper;
    @Autowired
    private VppDrParticipationMapper participationMapper;
    @Autowired
    private VppResourcePointMapper resourcePointMapper;
    @Autowired
    private VppBaselineService baselineService;

    @Override
    public DrMonitorSummaryVO getSummary(String startDate, String endDate, List<Long> siteIds) {
        Integer tenantId = SecurityUtils.getTenantId();
        LocalDateTime rangeStart = parseStart(startDate);
        LocalDateTime rangeEnd = parseEnd(endDate);
        requireSiteIds(siteIds);

        List<VppResourcePoint> peakResources = listPeakResources(siteIds, tenantId);
        DrMonitorSummaryVO vo = new DrMonitorSummaryVO();
        vo.setPeakResourceCount((long) peakResources.size());
        if (peakResources.isEmpty()) {
            return vo;
        }

        Set<Long> resourceIds = peakResources.stream().map(VppResourcePoint::getId).collect(Collectors.toSet());
        Map<Long, Long> resourceSiteMap = peakResources.stream()
                .collect(Collectors.toMap(VppResourcePoint::getId, VppResourcePoint::getSiteId, (a, b) -> a));

        List<VppDrParticipation> participations = listParticipations(resourceIds, tenantId);
        Map<Long, VppDrEvent> eventMap = loadEventMap(participations, tenantId);
        List<VppDrEvent> rangedEvents = filterEventsByTime(eventMap.values(), rangeStart, rangeEnd);

        Set<Long> countEventIds = rangedEvents.stream()
                .filter(e -> e.getEventStatus() != null
                        && (e.getEventStatus() == EVENT_STATUS_EXECUTING || e.getEventStatus() == EVENT_STATUS_ENDED))
                .map(VppDrEvent::getId)
                .collect(Collectors.toSet());
        Set<Long> endedEventIds = rangedEvents.stream()
                .filter(e -> Objects.equals(e.getEventStatus(), EVENT_STATUS_ENDED))
                .map(VppDrEvent::getId)
                .collect(Collectors.toSet());

        // 每个资源点：参与的执行中/已结束事件数
        Map<Long, Set<Long>> resourceCountEvents = new HashMap<>();
        // 每个资源点：参与的已结束事件
        Map<Long, Set<Long>> resourceEndedEvents = new HashMap<>();
        for (VppDrParticipation p : participations) {
            if (p.getResourceId() == null || p.getEventId() == null) {
                continue;
            }
            if (countEventIds.contains(p.getEventId())) {
                resourceCountEvents.computeIfAbsent(p.getResourceId(), k -> new HashSet<>()).add(p.getEventId());
            }
            if (endedEventIds.contains(p.getEventId())) {
                resourceEndedEvents.computeIfAbsent(p.getResourceId(), k -> new HashSet<>()).add(p.getEventId());
            }
        }

        Long countSum = 0L;
        for (VppResourcePoint resource : peakResources) {
            countSum += resourceCountEvents.getOrDefault(resource.getId(), Collections.emptySet()).size();
        }
        vo.setResponseCount(countSum);

        // 邀约按站点：容量与达标
        Set<Long> peakSiteIds = peakResources.stream().map(VppResourcePoint::getSiteId).collect(Collectors.toSet());
        List<VppDrInvitation> endedInvitations = listInvitations(endedEventIds, peakSiteIds, tenantId);
        Map<Long, List<VppDrInvitation>> invitationsBySite = endedInvitations.stream()
                .filter(i -> i.getSiteId() != null)
                .collect(Collectors.groupingBy(VppDrInvitation::getSiteId));

        BigDecimal capacitySum = BigDecimal.ZERO;
        BigDecimal rateSum = BigDecimal.ZERO;
        int rateResourceCount = 0;
        for (VppResourcePoint resource : peakResources) {
            Long siteId = resourceSiteMap.get(resource.getId());
            Set<Long> endedForResource = resourceEndedEvents.getOrDefault(resource.getId(), Collections.emptySet());
            List<VppDrInvitation> siteInvitations = invitationsBySite.getOrDefault(siteId, Collections.emptyList())
                    .stream()
                    .filter(i -> endedForResource.contains(i.getDrEventId()))
                    .collect(Collectors.toList());

            BigDecimal resourceCapacity = BigDecimal.ZERO;
            long qualified = 0;
            for (VppDrInvitation invitation : siteInvitations) {
                resourceCapacity = resourceCapacity.add(nz(invitation.getActualResponseCapacityKw()));
                if (isCompletionQualified(invitation.getResponseCompletionRate())) {
                    qualified++;
                }
            }
            capacitySum = capacitySum.add(resourceCapacity);
            if (!siteInvitations.isEmpty()) {
                rateSum = rateSum.add(BigDecimal.valueOf(qualified)
                        .divide(BigDecimal.valueOf(siteInvitations.size()), 8, RoundingMode.HALF_UP));
                rateResourceCount++;
            }
        }
        vo.setResponseCapacityKw(avg(capacitySum, peakResources.size()));
        if (rateResourceCount > 0) {
            vo.setHistoricalQualifiedRate(avg(rateSum, rateResourceCount));
        }
        return vo;
    }

    @Override
    public CommonPage<DrMonitorRecordVO> pageRecords(String startDate, String endDate,
                                                     List<Long> siteIds, Map<String, Object> params) {
        Integer tenantId = SecurityUtils.getTenantId();
        LocalDateTime rangeStart = parseStart(startDate);
        LocalDateTime rangeEnd = parseEnd(endDate);
        requireSiteIds(siteIds);

        List<VppResourcePoint> peakResources = listPeakResources(siteIds, tenantId);
        if (peakResources.isEmpty()) {
            Page<?> page = VppPageHelper.of(params);
            return new CommonPage<>(Collections.emptyList(), 0L, page.getCurrent(), page.getSize());
        }

        Set<Long> resourceIds = peakResources.stream().map(VppResourcePoint::getId).collect(Collectors.toSet());
        Set<Long> peakSiteIds = peakResources.stream().map(VppResourcePoint::getSiteId).collect(Collectors.toSet());
        List<VppDrParticipation> participations = listParticipations(resourceIds, tenantId);
        Map<Long, VppDrEvent> eventMap = loadEventMap(participations, tenantId);

        List<VppDrEvent> events = filterEventsByTime(eventMap.values(), rangeStart, rangeEnd).stream()
                .filter(e -> e.getEventStatus() != null
                        && (e.getEventStatus() == EVENT_STATUS_EXECUTING || e.getEventStatus() == EVENT_STATUS_ENDED))
                .sorted(Comparator.comparing(VppDrEvent::getStartTime, Comparator.nullsLast(Comparator.reverseOrder())))
                .collect(Collectors.toList());

        Set<Long> eventIds = events.stream().map(VppDrEvent::getId).collect(Collectors.toSet());
        List<VppDrInvitation> invitations = listInvitations(eventIds, peakSiteIds, tenantId);
        Map<Long, List<VppDrInvitation>> byEvent = invitations.stream()
                .filter(i -> i.getDrEventId() != null)
                .collect(Collectors.groupingBy(VppDrInvitation::getDrEventId));

        List<DrMonitorRecordVO> records = events.stream()
                .map(e -> toRecordVo(e, byEvent.getOrDefault(e.getId(), Collections.emptyList())))
                .collect(Collectors.toList());

        Page<?> page = VppPageHelper.of(params);
        long current = page.getCurrent();
        long size = page.getSize();
        int from = (int) Math.min((current - 1) * size, records.size());
        int to = (int) Math.min(from + size, records.size());
        List<DrMonitorRecordVO> pageRecords = from >= records.size()
                ? Collections.emptyList()
                : records.subList(from, to);
        return new CommonPage<>(pageRecords, (long) records.size(), current, size);
    }

    @Override
    public DrMonitorCurveVO getCurve(Long eventId, List<Long> siteIds) {
        if (eventId == null) {
            throw new BusinessException("事件ID不能为空");
        }
        requireSiteIds(siteIds);
        Integer tenantId = SecurityUtils.getTenantId();
        VppDrEvent event = eventMapper.selectById(eventId);
        if (event == null || VppAuditHelper.isDeleted(event.getDeleteFlag())) {
            throw new BusinessException("事件不存在");
        }
        if (tenantId != null && event.getTenantId() != null && !tenantId.equals(event.getTenantId())) {
            throw new BusinessException("事件不存在");
        }
        if (event.getStartTime() == null) {
            throw new BusinessException("事件缺少开始时间");
        }

        List<VppResourcePoint> peakResources = listPeakResources(siteIds, tenantId);
        if (peakResources.isEmpty()) {
            throw new BusinessException("所选站点下无可参与调峰的资源点");
        }
        int resourceCount = peakResources.size();
        Map<Long, Long> siteResourceCount = new HashMap<>();
        for (VppResourcePoint resource : peakResources) {
            siteResourceCount.merge(resource.getSiteId(), 1L, Long::sum);
        }
        Set<Long> peakSiteIds = siteResourceCount.keySet();

        List<VppDrInvitation> invitations = listInvitations(
                Collections.singleton(event.getId()), peakSiteIds, tenantId);
        BigDecimal declaredSum = BigDecimal.ZERO;
        BigDecimal actualSum = BigDecimal.ZERO;
        for (VppDrInvitation invitation : invitations) {
            declaredSum = declaredSum.add(nz(invitation.getDeclaredCapacityKw()));
            actualSum = actualSum.add(nz(invitation.getActualResponseCapacityKw()));
        }
        BigDecimal declaredAvg = avg(declaredSum, resourceCount);
        BigDecimal actualAvg = avg(actualSum, resourceCount);

        String responseStartTime = event.getStartTime().format(DATE_TIME_FMT);
        // 按资源点加权：站点曲线 × 该站可调峰资源数，再 / 总资源数
        Map<String, BigDecimal> baselineWeighted = new LinkedHashMap<>();
        Map<String, BigDecimal> loadWeighted = new LinkedHashMap<>();
        Set<String> dataSources = new LinkedHashSet<>();
        List<Long> usedSiteIds = new ArrayList<>();

        for (Map.Entry<Long, Long> entry : siteResourceCount.entrySet()) {
            Long siteId = entry.getKey();
            BigDecimal weight = BigDecimal.valueOf(entry.getValue());
            SiteBaselineVO baseline = baselineService.getSiteBaseline(siteId, responseStartTime);
            if (baseline == null || CollectionUtils.isEmpty(baseline.getPoints())) {
                continue;
            }
            usedSiteIds.add(siteId);
            if (StringUtils.hasText(baseline.getDataSource())) {
                dataSources.add(baseline.getDataSource());
            }
            for (BaselinePointVO point : baseline.getPoints()) {
                if (!StringUtils.hasText(point.getTime())) {
                    continue;
                }
                baselineWeighted.merge(point.getTime(),
                        nz(point.getPredictedBaselineKw()).multiply(weight), BigDecimal::add);
                loadWeighted.merge(point.getTime(),
                        nz(point.getTodayActualKw()).multiply(weight), BigDecimal::add);
            }
        }

        BigDecimal divisor = BigDecimal.valueOf(resourceCount);
        DrMonitorCurveVO curve = new DrMonitorCurveVO();
        curve.setId(event.getId());
        curve.setEventId(event.getEventId());
        curve.setResponseDate(event.getStartTime().toLocalDate().format(DATE_FMT));
        curve.setSiteIds(usedSiteIds);
        curve.setDeclaredCapacityKw(declaredAvg);
        curve.setActualResponseCapacityKw(actualAvg);
        if (dataSources.isEmpty()) {
            curve.setDataSource("mock");
        } else if (dataSources.size() == 1) {
            curve.setDataSource(dataSources.iterator().next());
        } else {
            curve.setDataSource("mixed");
        }

        List<String> times = new ArrayList<>(baselineWeighted.keySet());
        times.sort(Comparator.naturalOrder());
        for (String time : times) {
            DrMonitorCurveVO.Point point = new DrMonitorCurveVO.Point();
            point.setTime(time);
            point.setBaselineKw(baselineWeighted.getOrDefault(time, BigDecimal.ZERO)
                    .divide(divisor, SCALE, RoundingMode.HALF_UP));
            point.setLoadKw(loadWeighted.getOrDefault(time, BigDecimal.ZERO)
                    .divide(divisor, SCALE, RoundingMode.HALF_UP));
            point.setDeclaredCapacityKw(declaredAvg);
            point.setActualResponseCapacityKw(actualAvg);
            curve.getPoints().add(point);
        }
        return curve;
    }

    // ---------- loaders ----------

    private void requireSiteIds(List<Long> siteIds) {
        if (CollectionUtils.isEmpty(siteIds)) {
            throw new BusinessException("站点ID不能为空");
        }
    }

    private List<VppResourcePoint> listPeakResources(List<Long> siteIds, Integer tenantId) {
        return resourcePointMapper.selectList(new LambdaQueryWrapper<VppResourcePoint>()
                .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .eq(VppResourcePoint::getIsSupportPeak, SUPPORT_PEAK)
                .in(VppResourcePoint::getSiteId, siteIds)
                .eq(tenantId != null, VppResourcePoint::getTenantId, tenantId)
                .isNotNull(VppResourcePoint::getSiteId));
    }

    private List<VppDrParticipation> listParticipations(Set<Long> resourceIds, Integer tenantId) {
        if (CollectionUtils.isEmpty(resourceIds)) {
            return Collections.emptyList();
        }
        return participationMapper.selectList(new LambdaQueryWrapper<VppDrParticipation>()
                .in(VppDrParticipation::getResourceId, resourceIds)
                .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .eq(VppDrParticipation::getParticipateStatus, PARTICIPATE_ACCEPT)
                .eq(tenantId != null, VppDrParticipation::getTenantId, tenantId));
    }

    private Map<Long, VppDrEvent> loadEventMap(List<VppDrParticipation> participations, Integer tenantId) {
        Set<Long> eventIds = participations.stream()
                .map(VppDrParticipation::getEventId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (eventIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return eventMapper.selectList(new LambdaQueryWrapper<VppDrEvent>()
                        .in(VppDrEvent::getId, eventIds)
                        .eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                        .eq(tenantId != null, VppDrEvent::getTenantId, tenantId))
                .stream()
                .collect(Collectors.toMap(VppDrEvent::getId, e -> e, (a, b) -> a));
    }

    private List<VppDrEvent> filterEventsByTime(Iterable<VppDrEvent> events,
                                                LocalDateTime rangeStart, LocalDateTime rangeEnd) {
        List<VppDrEvent> result = new ArrayList<>();
        for (VppDrEvent event : events) {
            if (event.getStartTime() == null) {
                continue;
            }
            if (rangeStart != null && event.getStartTime().isBefore(rangeStart)) {
                continue;
            }
            if (rangeEnd != null && event.getStartTime().isAfter(rangeEnd)) {
                continue;
            }
            result.add(event);
        }
        return result;
    }

    private List<VppDrInvitation> listInvitations(Set<Long> eventIds, Set<Long> siteIds, Integer tenantId) {
        if (CollectionUtils.isEmpty(eventIds) || CollectionUtils.isEmpty(siteIds)) {
            return Collections.emptyList();
        }
        return invitationMapper.selectList(new LambdaQueryWrapper<VppDrInvitation>()
                .in(VppDrInvitation::getDrEventId, eventIds)
                .in(VppDrInvitation::getSiteId, siteIds)
                .eq(VppDrInvitation::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .eq(tenantId != null, VppDrInvitation::getTenantId, tenantId));
    }

    private DrMonitorRecordVO toRecordVo(VppDrEvent event, List<VppDrInvitation> invitations) {
        DrMonitorRecordVO vo = new DrMonitorRecordVO();
        BeanUtils.copyProperties(event, vo);
        if (CollectionUtils.isEmpty(invitations)) {
            vo.setResponseCompletionRate(null);
            vo.setQualified(false);
            return vo;
        }
        BigDecimal sum = BigDecimal.ZERO;
        int rateCount = 0;
        boolean allQualified = true;
        for (VppDrInvitation invitation : invitations) {
            BigDecimal rate = invitation.getResponseCompletionRate();
            if (rate != null) {
                sum = sum.add(rate);
                rateCount++;
            }
            if (!isCompletionQualified(rate)) {
                allQualified = false;
            }
        }
        if (rateCount > 0) {
            vo.setResponseCompletionRate(sum.divide(BigDecimal.valueOf(rateCount), SCALE, RoundingMode.HALF_UP));
        }
        vo.setQualified(allQualified && rateCount == invitations.size());
        return vo;
    }

    static boolean isCompletionQualified(BigDecimal rate) {
        if (rate == null) {
            return false;
        }
        BigDecimal scaled = rate.setScale(2, RoundingMode.HALF_UP);
        return scaled.compareTo(ONE.setScale(2, RoundingMode.HALF_UP)) == 0
                || scaled.compareTo(HUNDRED.setScale(2, RoundingMode.HALF_UP)) == 0;
    }

    private static BigDecimal avg(BigDecimal sum, int divisor) {
        if (divisor <= 0) {
            return BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP);
        }
        return nz(sum).divide(BigDecimal.valueOf(divisor), SCALE, RoundingMode.HALF_UP);
    }

    private static LocalDateTime parseStart(String date) {
        if (!StringUtils.hasText(date)) {
            throw new BusinessException("开始日期不能为空");
        }
        try {
            return LocalDate.parse(date.trim(), DATE_FMT).atStartOfDay();
        } catch (DateTimeParseException ex) {
            throw new BusinessException("开始日期格式无效，应为 yyyy-MM-dd");
        }
    }

    private static LocalDateTime parseEnd(String date) {
        if (!StringUtils.hasText(date)) {
            throw new BusinessException("结束日期不能为空");
        }
        try {
            return LocalDate.parse(date.trim(), DATE_FMT).atTime(LocalTime.MAX);
        } catch (DateTimeParseException ex) {
            throw new BusinessException("结束日期格式无效，应为 yyyy-MM-dd");
        }
    }

    private static BigDecimal nz(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
}
