package com.usky.vpp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.usky.common.core.exception.BusinessException;
import com.usky.vpp.constant.VppDrEventStatus;
import com.usky.vpp.constant.VppTsdbConstants;
import com.usky.vpp.domain.VppCustomer;
import com.usky.vpp.domain.VppDevice;
import com.usky.vpp.domain.VppDrEvent;
import com.usky.vpp.domain.VppDrExecution;
import com.usky.vpp.domain.VppDrParticipation;
import com.usky.vpp.domain.VppResourcePoint;
import com.usky.vpp.domain.VppSite;
import com.usky.vpp.mapper.VppCustomerMapper;
import com.usky.vpp.mapper.VppDeviceMapper;
import com.usky.vpp.mapper.VppDrEventMapper;
import com.usky.vpp.mapper.VppDrExecutionMapper;
import com.usky.vpp.mapper.VppDrParticipationMapper;
import com.usky.vpp.mapper.VppResourcePointMapper;
import com.usky.vpp.mapper.VppSiteMapper;
import com.usky.vpp.service.VppResponseMonitorService;
import com.usky.vpp.service.VppTsdbQueryService;
import com.usky.vpp.service.vo.ResponseCompareSeriesVO;
import com.usky.vpp.service.vo.ResponseCompareVO;
import com.usky.vpp.service.vo.ResponseLoadAnalysisVO;
import com.usky.vpp.service.vo.ResponseLoadPointVO;
import com.usky.vpp.service.vo.ResponseLoadStatRowVO;
import com.usky.vpp.service.vo.ResponseMonitorTreeNodeVO;
import com.usky.vpp.util.VppAuditHelper;
import com.usky.vpp.util.VppBaselineHelper;
import com.usky.vpp.util.VppResponseMonitorHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
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.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;

@Service
public class VppResponseMonitorServiceImpl implements VppResponseMonitorService {

    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final int EVENT_STATUS_ENDED = VppDrEventStatus.ENDED;

    @Autowired
    private VppSiteMapper siteMapper;
    @Autowired
    private VppCustomerMapper customerMapper;
    @Autowired
    private VppResourcePointMapper resourcePointMapper;
    @Autowired
    private VppDeviceMapper deviceMapper;
    @Autowired
    private VppDrEventMapper eventMapper;
    @Autowired
    private VppDrParticipationMapper participationMapper;
    @Autowired
    private VppDrExecutionMapper executionMapper;
    @Autowired
    private VppTsdbQueryService tsdbQueryService;

    @Override
    public List<ResponseMonitorTreeNodeVO> getResourceTree(String keyword) {
        List<VppSite> sites = siteMapper.selectList(new LambdaQueryWrapper<VppSite>()
                .eq(VppSite::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                .orderByAsc(VppSite::getSiteName));
        if (sites.isEmpty()) {
            return Collections.emptyList();
        }

        Set<Long> customerIds = sites.stream()
                .map(VppSite::getCustomerId)
                .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, List<VppSite>> sitesByCustomer = new LinkedHashMap<>();
        List<VppSite> orphanSites = new ArrayList<>();
        for (VppSite site : sites) {
            if (site.getCustomerId() == null) {
                orphanSites.add(site);
                continue;
            }
            sitesByCustomer.computeIfAbsent(site.getCustomerId(), k -> new ArrayList<>()).add(site);
        }

        List<ResponseMonitorTreeNodeVO> tree = new ArrayList<>();
        for (Map.Entry<Long, List<VppSite>> entry : sitesByCustomer.entrySet()) {
            VppCustomer customer = customerMap.get(entry.getKey());
            String customerName = customer != null ? customer.getCustomerName() : "未命名客户";
            if (!matchesKeyword(customerName, keyword) && !anySiteMatches(entry.getValue(), keyword)) {
                continue;
            }
            ResponseMonitorTreeNodeVO parent = new ResponseMonitorTreeNodeVO();
            parent.setNodeType("CUSTOMER");
            parent.setId(entry.getKey());
            parent.setName(customerName);
            for (VppSite site : entry.getValue()) {
                if (!matchesKeyword(site.getSiteName(), keyword) && !matchesKeyword(customerName, keyword)) {
                    continue;
                }
                parent.getChildren().add(toSiteNode(site, entry.getKey()));
            }
            if (!parent.getChildren().isEmpty()) {
                tree.add(parent);
            }
        }

        for (VppSite site : orphanSites) {
            if (!matchesKeyword(site.getSiteName(), keyword)) {
                continue;
            }
            ResponseMonitorTreeNodeVO node = toSiteNode(site, null);
            node.setNodeType("SITE");
            tree.add(node);
        }
        return tree;
    }

    @Override
    public ResponseLoadAnalysisVO getLoadAnalysis(Long siteId, String date, Long eventId) {
        if (siteId == null) {
            throw new BusinessException("请选择站点");
        }
        VppSite site = requireSite(siteId);
        LocalDate statDate = parseDate(date);

        ResponseLoadAnalysisVO result = new ResponseLoadAnalysisVO();
        result.setSiteId(siteId);
        result.setSiteName(site.getSiteName());
        result.setDate(statDate.format(DATE_FMT));

        List<VppResourcePoint> resources = listSiteResources(siteId);
        List<VppDevice> devices = loadDevices(resources);
        DrContext drContext = resolveDrContext(siteId, statDate, eventId);
        result.setEventId(drContext.event != null ? drContext.event.getId() : null);
        result.setEventName(drContext.event != null ? drContext.event.getEventName() : null);

        BigDecimal declared = VppResponseMonitorHelper.resolveDeclaredCapacity(
                drContext.event, drContext.participations, drContext.executions);

            CurveBuildResult curve = buildCurve(resources, devices, statDate, declared, drContext);
        result.setPoints(curve.points);
        result.setDataSource(curve.dataSource);
        result.setStatistics(VppResponseMonitorHelper.buildStatistics(curve.points, statDate));
        return result;
    }

    @Override
    public ResponseCompareVO compare(String siteIds, String date) {
        List<Long> ids = parseSiteIds(siteIds);
        if (ids.isEmpty()) {
            throw new BusinessException("请至少选择一个站点");
        }
        LocalDate statDate = parseDate(date);
        ResponseCompareVO result = new ResponseCompareVO();
        result.setDate(statDate.format(DATE_FMT));
        // String dataSource = "mock";
        String dataSource = null;

        for (Long siteId : ids) {
            VppSite site = requireSite(siteId);
            List<VppResourcePoint> resources = listSiteResources(siteId);
            List<VppDevice> devices = loadDevices(resources);
            CurveBuildResult curve = buildCurve(resources, devices, statDate, BigDecimal.ZERO, DrContext.empty());
            if ("tsdb".equals(curve.dataSource)) {
                dataSource = "tsdb";
            }

            ResponseCompareSeriesVO series = new ResponseCompareSeriesVO();
            series.setSiteId(siteId);
            series.setSiteName(site.getSiteName());
            for (ResponseLoadPointVO point : curve.points) {
                ResponseCompareSeriesVO.ResponseComparePointVO item =
                        new ResponseCompareSeriesVO.ResponseComparePointVO();
                item.setTime(point.getDatetime());
                item.setValue(point.getActualLoadKw());
                series.getPoints().add(item);
            }
            result.getSeries().add(series);
        }
        result.setDataSource(dataSource);
        return result;
    }

    private CurveBuildResult buildCurve(List<VppResourcePoint> resources,
                                        List<VppDevice> devices,
                                        LocalDate statDate,
                                        BigDecimal declaredCapacityKw,
                                        DrContext drContext) {
        List<String> deviceUuids = resolveDeviceUuids(resources, devices);
        if (deviceUuids.isEmpty()) {
            // return new CurveBuildResult(
            //         VppResponseMonitorHelper.buildMockCurve(resources, devices, siteId, statDate, declaredCapacityKw),
            //         "mock");
            return new CurveBuildResult(Collections.emptyList(), null);
        }

        // 确定响应时段（有事件用事件时段，无事件用全天）
        LocalDateTime responseStart;
        LocalDateTime responseEnd;
        if (drContext.event != null && drContext.event.getStartTime() != null) {
            responseStart = drContext.event.getStartTime();
            responseEnd = drContext.event.getEndTime() != null
                    ? drContext.event.getEndTime() : statDate.atTime(23, 59, 59);
        } else {
            responseStart = statDate.atStartOfDay();
            responseEnd = statDate.atTime(23, 59, 59);
        }

        // 选取典型历史日（与基线管理一致：法定节假日 + 调休，工作日5天 / 非工作日3天）
        Set<LocalDate> excludeDates = loadResponseHistoryDates();
        boolean workday = VppBaselineHelper.isWorkdayResponse(statDate);
        int requiredCount = workday
                ? VppBaselineHelper.WEEKDAY_REFERENCE_COUNT
                : VppBaselineHelper.NON_WEEKDAY_REFERENCE_COUNT;
        List<LocalDate> referenceDates = VppBaselineHelper.selectReferenceDates(
                statDate, workday, excludeDates, requiredCount);

        int interval = VppResponseMonitorHelper.BUCKET_MINUTES;

        // 查询原始基线（全天，多个典型历史日同时段均值）
        Map<String, BigDecimal> originalBaseline = tsdbQueryService.queryBaselineAvgByTimePoint(
                deviceUuids, referenceDates,
                LocalTime.MIN, LocalTime.of(23, 59, 59),
                VppBaselineHelper.POWER_METRICS, interval, null);
        if (originalBaseline.isEmpty()) {
            // return new CurveBuildResult(
            //         VppResponseMonitorHelper.buildMockCurve(resources, devices, siteId, statDate, declaredCapacityKw),
            //         "mock");
            return new CurveBuildResult(Collections.emptyList(), "tsdb");
        }

        // 今日实际负荷（全天）
        Map<String, BigDecimal> todayActual = VppBaselineHelper.buildActualLoadByTimePoint(
                tsdbQueryService.queryDeviceMetricHistory(
                        deviceUuids, statDate.atStartOfDay(), statDate.atTime(23, 59, 59),
                        VppBaselineHelper.POWER_METRICS),
                interval);

        // 修正系数 K：响应开始前2小时今日实测 / 同时段原始基线，限制 [0.7, 1.2]
        LocalDateTime correctionWindowStart = responseStart.minusHours(VppBaselineHelper.CORRECTION_HOURS);
        Map<String, BigDecimal> correctionBaseline = tsdbQueryService.queryBaselineAvgByTimePoint(
                deviceUuids, referenceDates,
                correctionWindowStart.toLocalTime(), responseStart.toLocalTime(),
                VppBaselineHelper.POWER_METRICS, interval, null);
        BigDecimal correctionFactorK = VppBaselineHelper.calculateCorrectionFactor(
                responseStart, todayActual,
                correctionBaseline.isEmpty() ? originalBaseline : correctionBaseline);

        // 预测基线 = 原始基线 × 修正系数 K
        Map<String, BigDecimal> predictedBaseline = new TreeMap<>();
        for (Map.Entry<String, BigDecimal> entry : originalBaseline.entrySet()) {
            predictedBaseline.put(entry.getKey(),
                    entry.getValue().multiply(correctionFactorK).setScale(2, RoundingMode.HALF_UP));
        }

        // TSDB 申报容量（仅当有事件时计算）
        BigDecimal tsdbDeclaredKw = BigDecimal.ZERO;
        if (drContext.event != null) {
            Map<String, BigDecimal> responseWindowBaseline = tsdbQueryService.queryBaselineAvgByTimePoint(
                    deviceUuids, referenceDates,
                    responseStart.toLocalTime(), responseEnd.toLocalTime(),
                    VppBaselineHelper.POWER_METRICS, interval, null);
            tsdbDeclaredKw = VppBaselineHelper.calculateDeclaredCapacityKw(
                    responseWindowBaseline, responseStart, responseEnd);
        }
        BigDecimal finalDeclared = tsdbDeclaredKw.compareTo(BigDecimal.ZERO) > 0
                ? tsdbDeclaredKw : declaredCapacityKw;

        List<ResponseLoadPointVO> points = VppResponseMonitorHelper.buildCurveWithBaseline(
                predictedBaseline, todayActual, statDate, finalDeclared, drContext.executionByTime, interval);
        return new CurveBuildResult(points, "tsdb");
    }

    private DrContext resolveDrContext(Long siteId, LocalDate statDate, Long eventId) {
        Set<Long> resourceIds = listSiteResources(siteId).stream()
                .map(VppResourcePoint::getId)
                .collect(Collectors.toSet());
        if (resourceIds.isEmpty()) {
            return DrContext.empty();
        }

        VppDrEvent event;
        if (eventId != null) {
            event = eventMapper.selectById(eventId);
            if (event == null || VppAuditHelper.isDeleted(event.getDeleteFlag())) {
                throw new BusinessException("需求响应事件不存在");
            }
        } else {
            LocalDateTime dayStart = statDate.atStartOfDay();
            LocalDateTime dayEnd = statDate.atTime(23, 59, 59);
            event = eventMapper.selectList(new LambdaQueryWrapper<VppDrEvent>()
                            .eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED)
                            .ge(VppDrEvent::getStartTime, dayStart)
                            .le(VppDrEvent::getStartTime, dayEnd)
                            .orderByDesc(VppDrEvent::getStartTime)
                            .last("LIMIT 1"))
                    .stream().findFirst().orElse(null);
        }
        if (event == null) {
            return DrContext.empty();
        }

        List<VppDrParticipation> participations = participationMapper.selectList(
                new LambdaQueryWrapper<VppDrParticipation>()
                        .eq(VppDrParticipation::getEventId, event.getId())
                        .and(w -> {
                            if (!resourceIds.isEmpty()) {
                                w.in(VppDrParticipation::getResourceId, resourceIds);
                            }
                            VppSite site = siteMapper.selectById(siteId);
                            if (site != null && site.getCustomerId() != null) {
                                if (!resourceIds.isEmpty()) {
                                    w.or().eq(VppDrParticipation::getCustomerId, site.getCustomerId());
                                } else {
                                    w.eq(VppDrParticipation::getCustomerId, site.getCustomerId());
                                }
                            }
                        })
                        .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED));
        List<VppDrExecution> executions = executionMapper.selectList(
                new LambdaQueryWrapper<VppDrExecution>()
                        .eq(VppDrExecution::getEventId, event.getId())
                        .in(VppDrExecution::getResourceId, resourceIds)
                        .eq(VppDrExecution::getDeleteFlag, VppAuditHelper.NOT_DELETED));

        Map<LocalDateTime, BigDecimal> executionByTime = new HashMap<>();
        for (VppDrExecution execution : executions) {
            if (execution.getActualKw() == null) {
                continue;
            }
            LocalDateTime key = execution.getStartedAt() != null
                    ? alignBucket(execution.getStartedAt())
                    : statDate.atStartOfDay();
            executionByTime.merge(key, execution.getActualKw(), BigDecimal::add);
        }
        return new DrContext(event, participations, executions, executionByTime);
    }

    private static LocalDateTime alignBucket(LocalDateTime time) {
        int minute = (time.getMinute() / VppResponseMonitorHelper.BUCKET_MINUTES)
                * VppResponseMonitorHelper.BUCKET_MINUTES;
        return time.withMinute(minute).withSecond(0).withNano(0);
    }

    /**
     * 查询历史已结束的 DR 事件日期，用于基线测算时排除。
     */
    private Set<LocalDate> loadResponseHistoryDates() {
        List<VppDrEvent> events = eventMapper.selectList(
                new LambdaQueryWrapper<VppDrEvent>()
                        .eq(VppDrEvent::getEventStatus, EVENT_STATUS_ENDED)
                        .eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED));
        Set<LocalDate> dates = new HashSet<>();
        for (VppDrEvent event : events) {
            if (event.getStartTime() != null) {
                dates.add(event.getStartTime().toLocalDate());
            }
        }
        return dates;
    }

    private ResponseMonitorTreeNodeVO toSiteNode(VppSite site, Long parentId) {
        ResponseMonitorTreeNodeVO node = new ResponseMonitorTreeNodeVO();
        node.setNodeType("SITE");
        node.setId(site.getId());
        node.setName(site.getSiteName());
        node.setParentId(parentId);
        return node;
    }

    private boolean anySiteMatches(List<VppSite> sites, String keyword) {
        if (!StringUtils.hasText(keyword)) {
            return true;
        }
        return sites.stream().anyMatch(s -> matchesKeyword(s.getSiteName(), keyword));
    }

    private boolean matchesKeyword(String text, String keyword) {
        return !StringUtils.hasText(keyword)
                || (text != null && text.toLowerCase().contains(keyword.toLowerCase()));
    }

    private VppSite requireSite(Long siteId) {
        VppSite site = siteMapper.selectById(siteId);
        if (site == null || VppAuditHelper.isDeleted(site.getDeleteFlag())) {
            throw new BusinessException("站点不存在");
        }
        return site;
    }

    private List<VppResourcePoint> listSiteResources(Long siteId) {
        return resourcePointMapper.selectList(new LambdaQueryWrapper<VppResourcePoint>()
                .eq(VppResourcePoint::getSiteId, siteId)
                .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED));
    }

    private List<VppDevice> loadDevices(List<VppResourcePoint> resources) {
        Set<Long> deviceIds = resources.stream()
                .map(VppResourcePoint::getDeviceId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (deviceIds.isEmpty()) {
            return Collections.emptyList();
        }
        return deviceMapper.selectBatchIds(deviceIds).stream()
                .filter(d -> !VppAuditHelper.isDeleted(d.getDeleteFlag()))
                .collect(Collectors.toList());
    }

    private List<String> resolveDeviceUuids(List<VppResourcePoint> resources, List<VppDevice> devices) {
        if (resources.isEmpty() || devices.isEmpty()) {
            return Collections.emptyList();
        }
        Map<Long, VppDevice> deviceMap = devices.stream()
                .collect(Collectors.toMap(VppDevice::getId, d -> d, (a, b) -> a));
        List<String> uuids = new ArrayList<>();
        for (VppResourcePoint resource : resources) {
            if (resource.getDeviceId() == null) {
                continue;
            }
            VppDevice device = deviceMap.get(resource.getDeviceId());
            if (device != null && StringUtils.hasText(device.getDeviceUuid())) {
                uuids.add(device.getDeviceUuid());
            }
        }
        return uuids;
    }

    private LocalDate parseDate(String date) {
        if (!StringUtils.hasText(date)) {
            return LocalDate.now();
        }
        try {
            return LocalDate.parse(date, DATE_FMT);
        } catch (DateTimeParseException ex) {
            throw new BusinessException("日期格式错误，请使用 yyyy-MM-dd");
        }
    }

    private List<Long> parseSiteIds(String siteIds) {
        if (!StringUtils.hasText(siteIds)) {
            return Collections.emptyList();
        }
        return Arrays.stream(siteIds.split(","))
                .map(String::trim)
                .filter(StringUtils::hasText)
                .map(Long::parseLong)
                .distinct()
                .sorted(Comparator.naturalOrder())
                .collect(Collectors.toList());
    }

    private static class CurveBuildResult {
        private final List<ResponseLoadPointVO> points;
        private final String dataSource;

        private CurveBuildResult(List<ResponseLoadPointVO> points, String dataSource) {
            this.points = points;
            this.dataSource = dataSource;
        }
    }

    private static class DrContext {
        private final VppDrEvent event;
        private final List<VppDrParticipation> participations;
        private final List<VppDrExecution> executions;
        private final Map<LocalDateTime, BigDecimal> executionByTime;

        private DrContext(VppDrEvent event,
                          List<VppDrParticipation> participations,
                          List<VppDrExecution> executions,
                          Map<LocalDateTime, BigDecimal> executionByTime) {
            this.event = event;
            this.participations = participations;
            this.executions = executions;
            this.executionByTime = executionByTime;
        }

        private static DrContext empty() {
            return new DrContext(null, Collections.emptyList(), Collections.emptyList(), Collections.emptyMap());
        }
    }
}
