package com.usky.vpp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.usky.vpp.domain.VppDevice;
import com.usky.vpp.domain.VppEnergyReadingMonthly;
import com.usky.vpp.domain.VppSettlementBill;
import com.usky.vpp.domain.VppSettlementBillDetail;
import com.usky.vpp.domain.VppSite;
import com.usky.vpp.enums.DeviceTypeMetricEnum;
import com.usky.vpp.enums.TimeSegmentEnum;
import com.usky.vpp.mapper.VppDeviceMapper;
import com.usky.vpp.mapper.VppEnergyReadingMonthlyMapper;
import com.usky.vpp.mapper.VppSettlementBillDetailMapper;
import com.usky.vpp.mapper.VppSettlementBillMapper;
import com.usky.vpp.mapper.VppSiteMapper;
import com.usky.vpp.service.VppSettlementSyncService;
import com.usky.vpp.service.VppTsdbQueryService;
import com.usky.vpp.util.VppAuditHelper;
import com.usky.vpp.util.VppEnergyUsageHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
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.YearMonth;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * 每月从时序库拉取站点分时电量，写入月度核算与电费账单（电费金额固定为 0）
 */
@Service
public class VppSettlementSyncServiceImpl implements VppSettlementSyncService {

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

    private static final int CALC_STATUS_PENDING = 0;
    private static final int PAYMENT_STATUS_UNPAID = 0;
    private static final int SCALE = 4;

    @Autowired
    private VppSiteMapper siteMapper;
    @Autowired
    private VppDeviceMapper deviceMapper;
    @Autowired
    private VppEnergyReadingMonthlyMapper energyReadingMonthlyMapper;
    @Autowired
    private VppSettlementBillMapper settlementBillMapper;
    @Autowired
    private VppSettlementBillDetailMapper settlementBillDetailMapper;
    @Autowired
    private VppTsdbQueryService vppTsdbQueryService;

    private TransactionTemplate transactionTemplate;

    @Autowired
    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    @Override
    public int syncLastMonth() {
        YearMonth lastMonth = YearMonth.now().minusMonths(1);
        return syncMonth(lastMonth.getYear(), lastMonth.getMonthValue());
    }

    @Override
    public int syncMonth(int settleYear, int settleMonth) {
        YearMonth yearMonth = YearMonth.of(settleYear, settleMonth);
        LocalDateTime startInclusive = yearMonth.atDay(1).atStartOfDay();
        LocalDateTime endExclusive = yearMonth.plusMonths(1).atDay(1).atStartOfDay();
        LocalDateTime queryEnd = endExclusive.minusSeconds(1);
        String settleYearStr = String.valueOf(settleYear);
        LocalDate dueDate = yearMonth.plusMonths(1).atEndOfMonth();

        List<VppSite> sites = siteMapper.selectList(new LambdaQueryWrapper<VppSite>()
                .eq(VppSite::getDeleteFlag, VppAuditHelper.NOT_DELETED));
        if (CollectionUtils.isEmpty(sites)) {
            log.info("月度结算同步：无可用站点, settle={}-{}", settleYear, settleMonth);
            return 0;
        }

        int success = 0;
        for (VppSite site : sites) {
            try {
                Boolean synced = transactionTemplate.execute(status ->
                        syncSite(site, settleYearStr, settleMonth, startInclusive, endExclusive, queryEnd, dueDate));
                if (Boolean.TRUE.equals(synced)) {
                    success++;
                }
            } catch (Exception ex) {
                log.error("月度结算同步失败 siteId={}, settle={}-{}, err={}",
                        site.getId(), settleYear, settleMonth, ex.getMessage(), ex);
            }
        }
        log.info("月度结算同步完成 settle={}-{}, sites={}, success={}",
                settleYear, settleMonth, sites.size(), success);
        return success;
    }

    private boolean syncSite(VppSite site,
                             String settleYear,
                             int settleMonth,
                             LocalDateTime startInclusive,
                             LocalDateTime endExclusive,
                             LocalDateTime queryEnd,
                             LocalDate dueDate) {
        if (site == null || site.getId() == null) {
            return false;
        }
        if (existsReading(site.getCustomerId(), site.getId(), settleYear, settleMonth)
                || existsBill(site.getCustomerId(), site.getId(), settleYear, settleMonth)) {
            log.info("月度结算已存在，跳过 siteId={}, settle={}-{}", site.getId(), settleYear, settleMonth);
            return false;
        }

        SiteEnergyAggregate aggregate = querySiteEnergy(site.getId(), startInclusive, endExclusive, queryEnd);

        VppEnergyReadingMonthly reading = buildReading(site, settleYear, settleMonth, aggregate);
        energyReadingMonthlyMapper.insert(reading);

        VppSettlementBill bill = buildBill(site, settleYear, settleMonth, reading.getTotalEnergyKwh(), dueDate);
        settlementBillMapper.insert(bill);

        for (VppSettlementBillDetail detail : buildBillDetails(bill.getId(), site.getTenantId(), aggregate)) {
            settlementBillDetailMapper.insert(detail);
        }
        return true;
    }

    private SiteEnergyAggregate querySiteEnergy(Long siteId,
                                                LocalDateTime startInclusive,
                                                LocalDateTime endExclusive,
                                                LocalDateTime queryEnd) {
        List<VppDevice> devices = deviceMapper.selectList(new LambdaQueryWrapper<VppDevice>()
                .eq(VppDevice::getSiteId, siteId)
                .eq(VppDevice::getDeleteFlag, VppAuditHelper.NOT_DELETED));
        if (CollectionUtils.isEmpty(devices)) {
            return SiteEnergyAggregate.empty();
        }

        Map<DeviceTypeMetricEnum, List<String>> typeDeviceUuids = new HashMap<>();
        for (VppDevice device : devices) {
            if (device == null || !StringUtils.hasText(device.getDeviceUuid())) {
                continue;
            }
            DeviceTypeMetricEnum.ofDeviceType(device.getDeviceType()).ifPresent(typeEnum ->
                    typeDeviceUuids.computeIfAbsent(typeEnum, key -> new ArrayList<>())
                            .add(device.getDeviceUuid().trim()));
        }
        if (typeDeviceUuids.isEmpty()) {
            log.warn("站点无匹配电量设备类型 siteId={}", siteId);
            return SiteEnergyAggregate.empty();
        }

        SiteEnergyAggregate aggregate = SiteEnergyAggregate.empty();
        for (Map.Entry<DeviceTypeMetricEnum, List<String>> entry : typeDeviceUuids.entrySet()) {
            DeviceTypeMetricEnum typeEnum = entry.getKey();
            List<String> deviceUuids = new ArrayList<>(new LinkedHashSet<>(entry.getValue()));
            Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history =
                    vppTsdbQueryService.queryDeviceMetricHistory(
                            deviceUuids, startInclusive, queryEnd, typeEnum.getMetrics());
            aggregate.merge(sumUsageBySegment(deviceUuids, history, startInclusive, endExclusive));
        }
        return aggregate;
    }

    private SiteEnergyAggregate sumUsageBySegment(List<String> deviceUuids,
                                                  Map<String, Map<String, TreeMap<LocalDateTime, BigDecimal>>> history,
                                                  LocalDateTime startInclusive,
                                                  LocalDateTime endExclusive) {
        SiteEnergyAggregate aggregate = SiteEnergyAggregate.empty();
        if (CollectionUtils.isEmpty(deviceUuids) || history == null || history.isEmpty()) {
            return aggregate;
        }

        Map<TimeSegmentEnum, BigDecimal> forward = new EnumMap<>(TimeSegmentEnum.class);
        Map<TimeSegmentEnum, BigDecimal> reverse = new EnumMap<>(TimeSegmentEnum.class);
        for (TimeSegmentEnum segment : TimeSegmentEnum.values()) {
            forward.put(segment, BigDecimal.ZERO);
            reverse.put(segment, BigDecimal.ZERO);
        }
        BigDecimal totalForward = BigDecimal.ZERO;
        BigDecimal totalReverse = BigDecimal.ZERO;
        boolean hasData = false;

        for (String deviceUuid : deviceUuids) {
            Map<String, TreeMap<LocalDateTime, BigDecimal>> metricMap = history.get(deviceUuid);
            if (metricMap == null || metricMap.isEmpty()) {
                continue;
            }
            for (TimeSegmentEnum segment : TimeSegmentEnum.values()) {
                BigDecimal fwd = VppEnergyUsageHelper.calcUsage(
                        metricMap.get(segment.getForwardMetric()), startInclusive, endExclusive);
                if (fwd != null) {
                    hasData = true;
                    forward.put(segment, forward.get(segment).add(fwd));
                }
                BigDecimal rev = VppEnergyUsageHelper.calcUsage(
                        metricMap.get(segment.getReverseMetric()), startInclusive, endExclusive);
                if (rev != null) {
                    hasData = true;
                    reverse.put(segment, reverse.get(segment).add(rev));
                }
            }
            BigDecimal epp = VppEnergyUsageHelper.calcUsage(metricMap.get("epp"), startInclusive, endExclusive);
            if (epp != null) {
                hasData = true;
                totalForward = totalForward.add(epp);
            }
            BigDecimal epn = VppEnergyUsageHelper.calcUsage(metricMap.get("epn"), startInclusive, endExclusive);
            if (epn != null) {
                hasData = true;
                totalReverse = totalReverse.add(epn);
            }
        }

        BigDecimal sharp = scale(forward.get(TimeSegmentEnum.SPIKE));
        BigDecimal peak = scale(forward.get(TimeSegmentEnum.PEAK));
        BigDecimal flat = scale(forward.get(TimeSegmentEnum.FLAT));
        BigDecimal valley = scale(forward.get(TimeSegmentEnum.VALLEY));
        BigDecimal segmentSum = sharp.add(peak).add(flat).add(valley);

        BigDecimal totalEnergy = totalForward.compareTo(BigDecimal.ZERO) > 0 ? scale(totalForward) : segmentSum;
        BigDecimal reverseSum = reverse.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal genEnergy = totalReverse.compareTo(BigDecimal.ZERO) > 0 ? scale(totalReverse) : scale(reverseSum);

        aggregate.hasData = hasData;
        aggregate.sharpEnergyKwh = sharp;
        aggregate.peakEnergyKwh = peak;
        aggregate.flatEnergyKwh = flat;
        aggregate.valleyEnergyKwh = valley;
        aggregate.totalEnergyKwh = totalEnergy;
        aggregate.genEnergyKwh = genEnergy;
        return aggregate;
    }

    private VppEnergyReadingMonthly buildReading(VppSite site,
                                                 String settleYear,
                                                 int settleMonth,
                                                 SiteEnergyAggregate aggregate) {
        VppEnergyReadingMonthly reading = new VppEnergyReadingMonthly();
        reading.setCustomerId(site.getCustomerId());
        reading.setSiteId(site.getId());
        reading.setSettleYear(settleYear);
        reading.setSettleMonth(settleMonth);
        reading.setSharpEnergyKwh(aggregate.sharpEnergyKwh);
        reading.setPeakEnergyKwh(aggregate.peakEnergyKwh);
        reading.setFlatEnergyKwh(aggregate.flatEnergyKwh);
        reading.setValleyEnergyKwh(aggregate.valleyEnergyKwh);
        reading.setTotalEnergyKwh(aggregate.totalEnergyKwh);
        reading.setGenEnergyKwh(aggregate.genEnergyKwh);
        reading.setCalcStatus(CALC_STATUS_PENDING);
        reading.setCalcAt(null);
        reading.setTenantId(site.getTenantId());
        VppAuditHelper.fillCreate(reading);
        if (reading.getTenantId() == null) {
            reading.setTenantId(site.getTenantId());
        }
        return reading;
    }

    private VppSettlementBill buildBill(VppSite site,
                                        String settleYear,
                                        int settleMonth,
                                        BigDecimal totalEnergyKwh,
                                        LocalDate dueDate) {
        VppSettlementBill bill = new VppSettlementBill();
        bill.setBillNo(VppAuditHelper.nextBillNo());
        bill.setCustomerId(site.getCustomerId());
        bill.setSiteId(site.getId());
        bill.setSettleYear(settleYear);
        bill.setSettleMonth(settleMonth);
        bill.setTotalEnergyKwh(VppEnergyUsageHelper.zeroIfNull(totalEnergyKwh));
        // 当前阶段电费统一为 0
        bill.setTotalAmount(BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP));
        bill.setPaidAmount(BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP));
        bill.setPaymentStatus(PAYMENT_STATUS_UNPAID);
        bill.setDueDate(dueDate);
        bill.setGeneratedAt(LocalDateTime.now());
        bill.setTenantId(site.getTenantId());
        VppAuditHelper.fillCreate(bill);
        if (bill.getTenantId() == null) {
            bill.setTenantId(site.getTenantId());
        }
        return bill;
    }

    private List<VppSettlementBillDetail> buildBillDetails(Long billId,
                                                           Integer tenantId,
                                                           SiteEnergyAggregate aggregate) {
        List<VppSettlementBillDetail> details = new ArrayList<>(4);
        details.add(buildDetail(billId, tenantId, TimeSegmentEnum.SPIKE, aggregate.sharpEnergyKwh));
        details.add(buildDetail(billId, tenantId, TimeSegmentEnum.PEAK, aggregate.peakEnergyKwh));
        details.add(buildDetail(billId, tenantId, TimeSegmentEnum.FLAT, aggregate.flatEnergyKwh));
        details.add(buildDetail(billId, tenantId, TimeSegmentEnum.VALLEY, aggregate.valleyEnergyKwh));
        return details;
    }

    private VppSettlementBillDetail buildDetail(Long billId,
                                                Integer tenantId,
                                                TimeSegmentEnum segment,
                                                BigDecimal energyKwh) {
        VppSettlementBillDetail detail = new VppSettlementBillDetail();
        detail.setBillId(billId);
        detail.setTimePeriod(segment.getPeriodCode());
        detail.setEnergyKwh(VppEnergyUsageHelper.zeroIfNull(energyKwh));
        detail.setPrice(BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP));
        detail.setAmount(BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP));
        detail.setTenantId(tenantId);
        return detail;
    }

    private boolean existsReading(Long customerId, Long siteId, String settleYear, int settleMonth) {
        return energyReadingMonthlyMapper.selectCount(new LambdaQueryWrapper<VppEnergyReadingMonthly>()
                .eq(VppEnergyReadingMonthly::getCustomerId, customerId)
                .eq(VppEnergyReadingMonthly::getSiteId, siteId)
                .eq(VppEnergyReadingMonthly::getSettleYear, settleYear)
                .eq(VppEnergyReadingMonthly::getSettleMonth, settleMonth)
                .eq(VppEnergyReadingMonthly::getDeleteFlag, VppAuditHelper.NOT_DELETED)) > 0;
    }

    private boolean existsBill(Long customerId, Long siteId, String settleYear, int settleMonth) {
        return settlementBillMapper.selectCount(new LambdaQueryWrapper<VppSettlementBill>()
                .eq(VppSettlementBill::getCustomerId, customerId)
                .eq(VppSettlementBill::getSiteId, siteId)
                .eq(VppSettlementBill::getSettleYear, settleYear)
                .eq(VppSettlementBill::getSettleMonth, settleMonth)
                .eq(VppSettlementBill::getDeleteFlag, VppAuditHelper.NOT_DELETED)) > 0;
    }

    private static BigDecimal scale(BigDecimal value) {
        return VppEnergyUsageHelper.zeroIfNull(value).setScale(SCALE, RoundingMode.HALF_UP);
    }

    private static final class SiteEnergyAggregate {
        private boolean hasData;
        private BigDecimal sharpEnergyKwh = BigDecimal.ZERO;
        private BigDecimal peakEnergyKwh = BigDecimal.ZERO;
        private BigDecimal flatEnergyKwh = BigDecimal.ZERO;
        private BigDecimal valleyEnergyKwh = BigDecimal.ZERO;
        private BigDecimal totalEnergyKwh = BigDecimal.ZERO;
        private BigDecimal genEnergyKwh = BigDecimal.ZERO;

        static SiteEnergyAggregate empty() {
            SiteEnergyAggregate aggregate = new SiteEnergyAggregate();
            aggregate.sharpEnergyKwh = BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP);
            aggregate.peakEnergyKwh = BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP);
            aggregate.flatEnergyKwh = BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP);
            aggregate.valleyEnergyKwh = BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP);
            aggregate.totalEnergyKwh = BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP);
            aggregate.genEnergyKwh = BigDecimal.ZERO.setScale(SCALE, RoundingMode.HALF_UP);
            return aggregate;
        }

        void merge(SiteEnergyAggregate other) {
            if (other == null) {
                return;
            }
            this.hasData = this.hasData || other.hasData;
            this.sharpEnergyKwh = this.sharpEnergyKwh.add(other.sharpEnergyKwh);
            this.peakEnergyKwh = this.peakEnergyKwh.add(other.peakEnergyKwh);
            this.flatEnergyKwh = this.flatEnergyKwh.add(other.flatEnergyKwh);
            this.valleyEnergyKwh = this.valleyEnergyKwh.add(other.valleyEnergyKwh);
            this.totalEnergyKwh = this.totalEnergyKwh.add(other.totalEnergyKwh);
            this.genEnergyKwh = this.genEnergyKwh.add(other.genEnergyKwh);
        }
    }
}
