package com.usky.vpp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.usky.common.security.utils.SecurityUtils;
import com.usky.vpp.constant.VppDrEventStatus;
import com.usky.vpp.domain.VppDevice;
import com.usky.vpp.domain.VppDrEvent;
import com.usky.vpp.domain.VppDrParticipation;
import com.usky.vpp.domain.VppResourcePoint;
import com.usky.vpp.domain.VppSite;
import com.usky.vpp.mapper.VppDeviceMapper;
import com.usky.vpp.mapper.VppDrEventMapper;
import com.usky.vpp.mapper.VppDrParticipationMapper;
import com.usky.vpp.mapper.VppResourcePointMapper;
import com.usky.vpp.mapper.VppSiteMapper;
import com.usky.vpp.service.VppSiteCompletionRateTaskService;
import com.usky.vpp.service.VppTsdbQueryService;
import com.usky.vpp.util.VppAuditHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class VppSiteCompletionRateTaskServiceImpl implements VppSiteCompletionRateTaskService {

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

    private static final int EVENT_STATUS_ENDED = VppDrEventStatus.ENDED;
    private static final int SITE_STATUS_ONLINE = 0;
    private static final int SITE_STATUS_OFFLINE = 1;
    private static final int SITE_STATUS_FAULT = 2;
    private static final int TSDB_DEVICE_STATUS_OFFLINE = 0;
    private static final int TSDB_DEVICE_STATUS_ONLINE = 1;

    @Autowired
    private VppDrEventMapper drEventMapper;
    @Autowired
    private VppDrParticipationMapper participationMapper;
    @Autowired
    private VppResourcePointMapper resourcePointMapper;
    @Autowired
    private VppDeviceMapper deviceMapper;
    @Autowired
    private VppSiteMapper siteMapper;
    @Autowired
    private VppTsdbQueryService vppTsdbQueryService;

    @Override
    public void refreshSiteAvgCompletionRate() {
        log.info("refreshSiteAvgCompletionRate start");

        Map<Long, BigDecimal> rateList = loadResourceCompletionRateMap();
        List<VppResourcePoint> resources = loadActiveResources();
        if (resources.isEmpty()) {
            log.info("refreshSiteAvgCompletionRate skipped: no active resource points");
            return;
        }

        Map<Long, List<VppResourcePoint>> siteResourcesMap = groupResourcesBySite(resources);
        Map<Long, VppDevice> deviceMap = loadDeviceMap(resources);
        Map<String, Map<String, BigDecimal>> tsdbMetricsMap = loadTsdbMetricsMap(deviceMap.values());

        int updatedCount = 0;
        for (Map.Entry<Long, List<VppResourcePoint>> entry : siteResourcesMap.entrySet()) {
            Long siteId = entry.getKey();
            List<VppResourcePoint> siteResources = entry.getValue();

            Integer runStatus = resolveSiteRunStatus(siteResources, deviceMap, tsdbMetricsMap);
            BigDecimal avgRate = averageCompletionRate(siteResources, rateList);

            LambdaUpdateWrapper<VppSite> updateWrapper = new LambdaUpdateWrapper<VppSite>()
                    .eq(VppSite::getId, siteId)
                    .set(VppSite::getRunStatus, runStatus);
            if (avgRate != null) {
                updateWrapper.set(VppSite::getAvgCompletionRate, avgRate);
            }
            siteMapper.update(null, updateWrapper);
            updatedCount++;
        }

        log.info("refreshSiteAvgCompletionRate finished, updatedSites={}", updatedCount);
    }

    private Map<Long, BigDecimal> loadResourceCompletionRateMap() {
        LambdaQueryWrapper<VppDrEvent> eventWrapper = new LambdaQueryWrapper<VppDrEvent>()
                .select(VppDrEvent::getId)
                .eq(VppDrEvent::getEventStatus, EVENT_STATUS_ENDED)
                .eq(VppDrEvent::getDeleteFlag, VppAuditHelper.NOT_DELETED);
        Integer tenantId = SecurityUtils.getTenantId();
        if (tenantId != null) {
            eventWrapper.eq(VppDrEvent::getTenantId, tenantId);
        }
        List<Long> endedEventIds = drEventMapper.selectList(eventWrapper)
                .stream()
                .map(VppDrEvent::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (endedEventIds.isEmpty()) {
            return Collections.emptyMap();
        }

        List<VppDrParticipation> participations = participationMapper.selectList(
                new LambdaQueryWrapper<VppDrParticipation>()
                        .select(VppDrParticipation::getResourceId, VppDrParticipation::getCompletionRate)
                        .in(VppDrParticipation::getEventId, endedEventIds)
                        .isNotNull(VppDrParticipation::getResourceId)
                        .isNotNull(VppDrParticipation::getCompletionRate)
                        .eq(VppDrParticipation::getDeleteFlag, VppAuditHelper.NOT_DELETED));
        if (CollectionUtils.isEmpty(participations)) {
            return Collections.emptyMap();
        }

        Map<Long, List<BigDecimal>> groupedRates = new HashMap<>();
        for (VppDrParticipation participation : participations) {
            groupedRates.computeIfAbsent(participation.getResourceId(), key -> new ArrayList<>())
                    .add(participation.getCompletionRate());
        }

        Map<Long, BigDecimal> rateList = new HashMap<>();
        groupedRates.forEach((resourceId, rates) -> {
            BigDecimal avg = averageValues(rates);
            if (avg != null) {
                rateList.put(resourceId, avg);
            }
        });
        return rateList;
    }

    private List<VppResourcePoint> loadActiveResources() {
        LambdaQueryWrapper<VppResourcePoint> wrapper = new LambdaQueryWrapper<VppResourcePoint>()
                .eq(VppResourcePoint::getDeleteFlag, VppAuditHelper.NOT_DELETED);
        Integer tenantId = SecurityUtils.getTenantId();
        if (tenantId != null) {
            wrapper.eq(VppResourcePoint::getTenantId, tenantId);
        }
        return resourcePointMapper.selectList(wrapper);
    }

    private Map<Long, List<VppResourcePoint>> groupResourcesBySite(List<VppResourcePoint> resources) {
        Map<Long, List<VppResourcePoint>> siteResourcesMap = new HashMap<>();
        for (VppResourcePoint resource : resources) {
            if (resource.getSiteId() == null) {
                continue;
            }
            siteResourcesMap.computeIfAbsent(resource.getSiteId(), key -> new ArrayList<>()).add(resource);
        }
        return siteResourcesMap;
    }

    private Map<Long, VppDevice> loadDeviceMap(List<VppResourcePoint> resources) {
        Set<Long> deviceIds = resources.stream()
                .map(VppResourcePoint::getDeviceId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (deviceIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return deviceMapper.selectBatchIds(deviceIds).stream()
                .filter(device -> !VppAuditHelper.isDeleted(device.getDeleteFlag()))
                .collect(Collectors.toMap(VppDevice::getId, device -> device, (a, b) -> a));
    }

    private Map<String, Map<String, BigDecimal>> loadTsdbMetricsMap(Collection<VppDevice> devices) {
        List<String> deviceUuids = devices.stream()
                .map(VppDevice::getDeviceUuid)
                .filter(StringUtils::hasText)
                .distinct()
                .collect(Collectors.toList());
        if (deviceUuids.isEmpty()) {
            return Collections.emptyMap();
        }
        return vppTsdbQueryService.queryLatestMetrics(deviceUuids,
                Collections.singletonList("device_status"));
    }

    private Integer resolveSiteRunStatus(List<VppResourcePoint> siteResources,
                                         Map<Long, VppDevice> deviceMap,
                                         Map<String, Map<String, BigDecimal>> tsdbMetricsMap) {
        List<Integer> deviceStatuses = new ArrayList<>();
        for (VppResourcePoint resource : siteResources) {
            VppDevice device = resolveDeviceForResource(resource, deviceMap);
            Map<String, BigDecimal> tsdbMetrics = resolveTsdbMetrics(device, tsdbMetricsMap);
            deviceStatuses.add(resolveDeviceOnlineStatus(tsdbMetrics));
        }
        return resolveSiteRunStatus(deviceStatuses);
    }

    private VppDevice resolveDeviceForResource(VppResourcePoint resource, Map<Long, VppDevice> deviceMap) {
        if (resource.getDeviceId() == null) {
            return null;
        }
        VppDevice device = deviceMap.get(resource.getDeviceId());
        return matchesResourceSite(device, resource.getSiteId()) ? device : null;
    }

    private boolean matchesResourceSite(VppDevice device, Long siteId) {
        return device != null && siteId != null && siteId.equals(device.getSiteId());
    }

    private Map<String, BigDecimal> resolveTsdbMetrics(VppDevice device,
                                                       Map<String, Map<String, BigDecimal>> tsdbMetricsMap) {
        if (device == null || !StringUtils.hasText(device.getDeviceUuid())) {
            return Collections.emptyMap();
        }
        Map<String, BigDecimal> metrics = tsdbMetricsMap.get(device.getDeviceUuid());
        return metrics != null ? metrics : Collections.emptyMap();
    }

    private Integer resolveDeviceOnlineStatus(Map<String, BigDecimal> tsdbMetrics) {
        if (CollectionUtils.isEmpty(tsdbMetrics) || tsdbMetrics.get("device_status") == null) {
            return null;
        }
        int status = tsdbMetrics.get("device_status").intValue();
        if (status == TSDB_DEVICE_STATUS_OFFLINE) {
            return TSDB_DEVICE_STATUS_OFFLINE;
        }
        if (status == TSDB_DEVICE_STATUS_ONLINE) {
            return TSDB_DEVICE_STATUS_ONLINE;
        }
        return null;
    }

    private Integer resolveSiteRunStatus(List<Integer> deviceStatuses) {
        if (CollectionUtils.isEmpty(deviceStatuses)) {
            return SITE_STATUS_FAULT;
        }
        boolean allOnline = deviceStatuses.stream().allMatch(status -> status != null && status == TSDB_DEVICE_STATUS_ONLINE);
        boolean allOffline = deviceStatuses.stream().allMatch(status -> status != null && status == TSDB_DEVICE_STATUS_OFFLINE);
        if (allOnline) {
            return SITE_STATUS_ONLINE;
        }
        if (allOffline) {
            return SITE_STATUS_OFFLINE;
        }
        return SITE_STATUS_FAULT;
    }

    private BigDecimal averageCompletionRate(List<VppResourcePoint> siteResources, Map<Long, BigDecimal> rateList) {
        if (CollectionUtils.isEmpty(siteResources) || CollectionUtils.isEmpty(rateList)) {
            return null;
        }
        List<BigDecimal> matchedRates = siteResources.stream()
                .map(VppResourcePoint::getId)
                .map(rateList::get)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        return averageValues(matchedRates);
    }

    private BigDecimal averageValues(List<BigDecimal> values) {
        if (CollectionUtils.isEmpty(values)) {
            return null;
        }
        List<BigDecimal> nonNullValues = values.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (nonNullValues.isEmpty()) {
            return null;
        }
        BigDecimal sum = nonNullValues.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
        return sum.divide(BigDecimal.valueOf(nonNullValues.size()), 4, RoundingMode.HALF_UP);
    }
}
