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.cache.KymCache;
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 BigDecimal WITHDRAWAL_FEE_RATE = new BigDecimal("0.006");
    private static final DateTimeFormatter PERIOD_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
    private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    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);

        // 汇总本期各站点分账数据
        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) {
            if ("000".equals(stationId)) {
                continue;
            }
            doStationSettlement(stationId, period, allRecords);
        }

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

    private void doStationSettlement(String stationId, String period, List<SplitRecord> allRecords) {
        // 站点+周期幂等：已有结算记录（不论状态）则跳过
        long existing = lambdaQuery()
                .eq(SettlementRecord::getStationId, stationId)
                .eq(SettlementRecord::getSettlementPeriod, period)
                .count();
        if (existing > 0) {
            log.info("站点 {} 周期 {} 已有结算记录，跳过", stationId, period);
            return;
        }

        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 netInflow = totalRecharge - totalRefund - totalCrossExpend + totalCrossIncome;
        int withdrawalFee = netInflow > 0
                ? new BigDecimal(netInflow).multiply(WITHDRAWAL_FEE_RATE).setScale(0, RoundingMode.DOWN).intValue()
                : 0;
        int afterWechat = openingBalance + netInflow - withdrawalFee;
        int platformFee = afterWechat > 0
                ? new BigDecimal(afterWechat).multiply(PLATFORM_FEE_RATE).setScale(0, RoundingMode.DOWN).intValue()
                : 0;
        int settlementAmount = afterWechat - platformFee;

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

        if (settlementAmount > 0) {
            record.setSettlementAmount(settlementAmount)
                    .setClosingPendingBalance(0)
                    .setStatus(SettlementRecord.STATUS_待结算);
        } else {
            record.setSettlementAmount(0)
                    .setClosingPendingBalance(settlementAmount)
                    .setStatus(SettlementRecord.STATUS_异常结算)
                    .setRemark("结算金额为负，结转至下期");
        }

        save(record);

        log.info("站点 {} 周期 {} 结算记录已生成，状态：{}，金额：{} 分，平台费：{} 分，提现手续费：{} 分",
                stationId, period, record.getStatus(), record.getSettlementAmount(), platformFee, withdrawalFee);
    }

    /**
     * 获取上期期末余额作为本期期初
     */
    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);
            vo.setStationName(KymCache.INSTANCE.getStationNameById(item.getStationId()));
            if (item.getCreateTime() != null) {
                vo.setCreateTime(item.getCreateTime().format(DTF));
            }
            if (item.getUpdateTime() != null) {
                vo.setUpdateTime(item.getUpdateTime().format(DTF));
            }
            return vo;
        }).toList();
        return new PageBean<>(voList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void confirmSettlement(Long recordId) {
        var record = getById(recordId);
        if (record == null) {
            throw new IllegalArgumentException("结算记录不存在");
        }
        if (record.getStatus() == SettlementRecord.STATUS_已结算) {
            throw new IllegalArgumentException("该记录已确认，不可重复操作");
        }
        if (record.getStatus() != SettlementRecord.STATUS_待结算) {
            throw new IllegalArgumentException("仅待结算记录可确认");
        }

        // 幂等门闩：条件更新（仅待结算可确认），并发重复确认时第二个事务更新 0 行直接失败，
        // 余额转入与平台费记录不会重复执行
        boolean claimed = lambdaUpdate()
                .set(SettlementRecord::getStatus, SettlementRecord.STATUS_已结算)
                .eq(SettlementRecord::getId, recordId)
                .eq(SettlementRecord::getStatus, SettlementRecord.STATUS_待结算)
                .update();
        if (!claimed) {
            throw new IllegalArgumentException("该记录已确认，不可重复操作");
        }

        // 转入站点可提现余额
        stationAccountService.lambdaUpdate()
                .setSql("available_balance = available_balance + {0}", record.getSettlementAmount())
                .eq(StationAccount::getStationId, record.getStationId())
                .update();

        // 平台服务费收入记录
        if (record.getPlatformFee() != null && record.getPlatformFee() > 0) {
            platformRevenueRecordService.saveRecord(record, record.getStationId());
            platformAccountService.addRevenue(record.getPlatformFee());
        }

        log.info("结算确认完成，记录ID：{}，站点：{}，周期：{}，金额：{} 分",
                recordId, record.getStationId(), record.getSettlementPeriod(), record.getSettlementAmount());
    }
}
