package com.kym.service.impl;

import com.github.pagehelper.PageHelper;
import com.kym.common.utils.CommUtil;
import com.kym.entity.SettlementRecord;
import com.kym.entity.SplitRecord;
import com.kym.entity.StationAccount;
import com.kym.entity.common.PageBean;
import com.kym.entity.queryParams.SettlementQueryParam;
import com.kym.entity.vo.SettlementRecordVo;
import com.kym.mapper.SettlementRecordMapper;
import com.kym.service.SettlementService;
import com.kym.service.PlatformAccountService;
import com.kym.service.PlatformRevenueRecordService;
import com.kym.service.SplitRecordService;
import com.kym.service.StationAccountService;
import com.kym.service.mybatisplus.MyBaseServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 结算服务实现
 *
 * @author skyline
 * @since 2026-05-14
 */
@Slf4j
@Service
public class SettlementServiceImpl extends MyBaseServiceImpl<SettlementRecordMapper, SettlementRecord> implements SettlementService {

    private static final BigDecimal PLATFORM_FEE_RATE = new BigDecimal("0.1");
    private static final DateTimeFormatter PERIOD_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");

    private final SplitRecordService splitRecordService;
    private final StationAccountService stationAccountService;
    private final PlatformAccountService platformAccountService;
    private final PlatformRevenueRecordService platformRevenueRecordService;

    public SettlementServiceImpl(SplitRecordService splitRecordService, StationAccountService stationAccountService,
                                 PlatformAccountService platformAccountService, PlatformRevenueRecordService platformRevenueRecordService) {
        this.splitRecordService = splitRecordService;
        this.stationAccountService = stationAccountService;
        this.platformAccountService = platformAccountService;
        this.platformRevenueRecordService = platformRevenueRecordService;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void executeMonthlySettlement() {
        YearMonth lastMonth = YearMonth.now().minusMonths(1);
        String period = lastMonth.format(PERIOD_FORMATTER);
        LocalDateTime periodStart = lastMonth.atDay(1).atStartOfDay();
        LocalDateTime periodEnd = lastMonth.atEndOfMonth().atTime(LocalTime.MAX);

        log.info("开始执行结算，周期：{}", period);

        // 幂等检查：如果本期已有结算记录则跳过
        long settledCount = lambdaQuery().eq(SettlementRecord::getSettlementPeriod, period).count();
        if (settledCount > 0) {
            log.warn("本期 {} 已有 {} 条结算记录，跳过重复结算", period, settledCount);
            return;
        }

        // 汇总本期各站点分账数据
        var allRecords = splitRecordService.lambdaQuery()
                .ge(SplitRecord::getCreateTime, periodStart)
                .le(SplitRecord::getCreateTime, periodEnd)
                .list();

        // 站点 -> {recharge, refund, crossExpend, crossIncome}
        Set<String> stationIds = allRecords.stream()
                .flatMap(r -> List.of(r.getFromStationId(), r.getToStationId()).stream())
                .collect(Collectors.toSet());

        for (String stationId : stationIds) {
            doStationSettlement(stationId, period, allRecords);
        }

        log.info("结算完成，周期：{}，涉及站点数：{}", period, stationIds.size());
    }

    private void doStationSettlement(String stationId, String period, List<SplitRecord> allRecords) {
        int totalRecharge = sumByType(allRecords, stationId, SplitRecord.TYPE_RECHARGE, true);
        int totalRefund = sumByType(allRecords, stationId, SplitRecord.TYPE_REFUND, false);
        int totalCrossExpend = sumByType(allRecords, stationId, SplitRecord.TYPE_CROSS_EXPEND, false);
        int totalCrossIncome = sumByType(allRecords, stationId, SplitRecord.TYPE_CROSS_INCOME, true);

        if (totalRecharge == 0 && totalRefund == 0 && totalCrossExpend == 0 && totalCrossIncome == 0) {
            return;
        }

        // 读取上期期末余额作为本期期初
        int openingBalance = getOpeningBalance(stationId, period);

        // 平台服务费基数（仅基于本期流入，不含上期结转）
        int feeBase = totalRecharge - totalRefund - totalCrossExpend + totalCrossIncome;
        int platformFee = feeBase > 0 ? new BigDecimal(feeBase).multiply(PLATFORM_FEE_RATE).setScale(0, RoundingMode.DOWN).intValue() : 0;

        // 可结算总额 = 期初结转 + 本期流入 - 平台费
        int available = openingBalance + feeBase - platformFee;

        SettlementRecord record = new SettlementRecord()
                .setStationId(stationId)
                .setSettlementPeriod(period)
                .setTotalRecharge(totalRecharge)
                .setTotalRefund(totalRefund)
                .setTotalCrossIncome(totalCrossIncome)
                .setTotalCrossExpend(totalCrossExpend)
                .setOpeningPendingBalance(openingBalance)
                .setPlatformFeeBase(feeBase)
                .setPlatformFee(platformFee);

        if (available > 0) {
            // 正常结算
            record.setSettlementAmount(available)
                    .setClosingPendingBalance(0)
                    .setStatus(SettlementRecord.STATUS_已结算);

            // 增加站点可提现金额
            stationAccountService.lambdaUpdate()
                    .setSql("available_balance = available_balance + {0}", available)
                    .eq(StationAccount::getStationId, stationId)
                    .update();

            save(record);

            // 平台服务费收入记录
            if (platformFee > 0) {
                platformRevenueRecordService.saveRecord(record, stationId);
                platformAccountService.addRevenue(platformFee);
            }

            log.info("站点 {} 结算成功，金额：{} 分，平台费：{} 分", stationId, available, platformFee);
        } else {
            // 异常结算
            record.setSettlementAmount(0)
                    .setClosingPendingBalance(available)
                    .setStatus(SettlementRecord.STATUS_异常结算)
                    .setRemark("结算金额为负，结转至下期");

            save(record);

            log.warn("站点 {} 异常结算，结转金额：{} 分", stationId, available);
        }
    }

    /**
     * 获取上期期末余额作为本期期初
     */
    private int getOpeningBalance(String stationId, String currentPeriod) {
        SettlementRecord lastRecord = lambdaQuery()
                .eq(SettlementRecord::getStationId, stationId)
                .orderByDesc(SettlementRecord::getSettlementPeriod)
                .last("LIMIT 1")
                .one();
        return lastRecord != null ? lastRecord.getClosingPendingBalance() : 0;
    }

    /**
     * 按类型汇总金额
     * @param isIncome true: 按 toStationId 汇总，false: 按 fromStationId 汇总
     */
    private int sumByType(List<SplitRecord> records, String stationId, Integer type, boolean isIncome) {
        return records.stream()
                .filter(r -> type.equals(r.getType()))
                .filter(r -> isIncome ? stationId.equals(r.getToStationId()) : stationId.equals(r.getFromStationId()))
                .mapToInt(r -> r.getAmount() != null ? r.getAmount() : 0)
                .sum();
    }

    @Override
    public PageBean<SettlementRecordVo> listSettlementRecords(SettlementQueryParam params) {
        PageHelper.startPage(params.getPageNum(), params.getPageSize());
        var res = lambdaQuery()
                .eq(CommUtil.isNotEmptyAndNull(params.getStationId()), SettlementRecord::getStationId, params.getStationId())
                .eq(CommUtil.isNotEmptyAndNull(params.getSettlementPeriod()), SettlementRecord::getSettlementPeriod, params.getSettlementPeriod())
                .eq(CommUtil.isNotEmptyAndNull(params.getStatus()), SettlementRecord::getStatus, params.getStatus())
                .orderByDesc(SettlementRecord::getSettlementPeriod)
                .orderByDesc(SettlementRecord::getId)
                .list();
        var voList = res.stream().map(item -> {
            var vo = new SettlementRecordVo();
            BeanUtils.copyProperties(item, vo);
            return vo;
        }).toList();
        return new PageBean<>(voList);
    }
}
