package com.kym.service.impl;

import com.kym.entity.SettlementRecord;
import com.kym.entity.SplitRecord;
import com.kym.entity.StationAccount;
import com.kym.service.PlatformAccountService;
import com.kym.service.PlatformRevenueRecordService;
import com.kym.service.SplitRecordService;
import com.kym.service.StationAccountService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * 结算服务单元测试
 * 核心验证：公式 平台费基数 = 总充值 - 总退款 - 跨店支出 + 跨店收入
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("结算服务")
class SettlementServiceImplTest {

    @Mock
    private SplitRecordService splitRecordService;
    @Mock
    private StationAccountService stationAccountService;
    @Mock
    private PlatformAccountService platformAccountService;
    @Mock
    private PlatformRevenueRecordService platformRevenueRecordService;

    private SettlementServiceImpl settlementService;
    private List<SplitRecord> records;

    @BeforeEach
    void setUp() {
        settlementService = new SettlementServiceImpl(splitRecordService, stationAccountService, platformAccountService, platformRevenueRecordService);
        records = new ArrayList<>();
    }

    // ---------- helpers ----------

    private SplitRecord recharge(String stationId, int amount) {
        return new SplitRecord()
                .setFromStationId(stationId).setToStationId(stationId)
                .setAmount(amount).setType(SplitRecord.TYPE_RECHARGE)
                .setTradeNo("RECHARGE_" + System.nanoTime());
    }

    private SplitRecord refund(String stationId, int amount) {
        return new SplitRecord()
                .setFromStationId(stationId).setToStationId(stationId)
                .setAmount(amount).setType(SplitRecord.TYPE_REFUND)
                .setTradeNo("REFUND_" + System.nanoTime());
    }

    private SplitRecord crossExpend(String fromStation, String toStation, int amount) {
        return new SplitRecord()
                .setFromStationId(fromStation).setToStationId(toStation)
                .setAmount(amount).setType(SplitRecord.TYPE_CROSS_EXPEND)
                .setTradeNo("CROSS_OUT_" + System.nanoTime());
    }

    private SplitRecord crossIncome(String fromStation, String toStation, int amount) {
        return new SplitRecord()
                .setFromStationId(fromStation).setToStationId(toStation)
                .setAmount(amount).setType(SplitRecord.TYPE_CROSS_INCOME)
                .setTradeNo("CROSS_IN_" + System.nanoTime());
    }

    private void givenNoPreviousSettlement() {
        when(splitRecordService.lambdaQuery()).thenReturn(null);
        // first call = getOpeningBalance → returns null (no previous)
        // settlement save → just verify
    }

    // ---------- tests ----------

    @Nested
    @DisplayName("正常结算场景")
    class NormalSettlement {

        @Test
        @DisplayName("本站充值本站消费 — 无退款无跨店")
        void onlyRecharge() {
            records.add(recharge("A", 10_000));

            // 平台费基数 = 10000 - 0 - 0 + 0 = 10000
            // 平台费 = 1000, 结算 = 9000
            assertPlatformFee(10_000, 0, 0, 0, 1_000);
            assertSettlementAmount(10_000, 0, 0, 0, 9_000);
        }

        @Test
        @DisplayName("含退款 — 退款不计平台费")
        void rechargeWithRefund() {
            records.add(recharge("A", 10_000));
            records.add(refund("A", 2_000));

            // 基数 = 10000 - 2000 = 8000, 费 = 800, 结算 = 7200
            assertPlatformFee(10_000, 2_000, 0, 0, 800);
            assertSettlementAmount(10_000, 2_000, 0, 0, 7_200);
        }

        @Test
        @DisplayName("跨店消费 70:30 — 充值方承担平台费")
        void crossStore70_30() {
            // A站充值10000，B站消费1000 → A→B 转账700
            records.add(recharge("A", 10_000));
            records.add(crossExpend("A", "B", 700));
            records.add(crossIncome("A", "B", 700));

            // A: 基数 = 10000 - 0 - 700 + 0 = 9300, 费 = 930
            assertPlatformFee(10_000, 0, 700, 0, 930);
            // B: 基数 = 0 - 0 - 0 + 700 = 700, 费 = 70
            assertPlatformFee(0, 0, 0, 700, 70);
        }

        @Test
        @DisplayName("完整场景 — 充值+退款+跨店")
        void fullScenario() {
            records.add(recharge("A", 10_000));
            records.add(refund("A", 500));
            records.add(crossExpend("A", "B", 700));
            records.add(crossIncome("A", "B", 700));

            // A: 基数 = 10000 - 500 - 700 + 0 = 8800, 费 = 880, 结算 = 7920
            assertPlatformFee(10_000, 500, 700, 0, 880);
            assertSettlementAmount(10_000, 500, 700, 0, 7_920);

            // B: 基数 = 0 - 0 - 0 + 700 = 700, 费 = 70, 结算 = 630
            assertPlatformFee(0, 0, 0, 700, 70);
            assertSettlementAmount(0, 0, 0, 700, 630);
        }

        @Test
        @DisplayName("平台费取整 — 向下取整")
        void platformFeeRoundingDown() {
            // 基数 333 → 平台费 33.3 → 向下取整 33
            records.add(recharge("A", 333));
            assertPlatformFee(333, 0, 0, 0, 33);
        }
    }

    @Nested
    @DisplayName("异常结算场景")
    class AbnormalSettlement {

        @Test
        @DisplayName("退款大于充值 — 结算金额为负 → 异常结算")
        void refundExceedsRecharge() {
            records.add(recharge("A", 1_000));
            records.add(refund("A", 3_000));

            // 基数 = 1000 - 3000 = -2000, 费 = 0
            int base = 1_000 - 3_000;
            assertTrue(base < 0, "基数应为负");
            assertEquals(0, calcPlatformFee(1000, 3000, 0, 0), "负基数平台费应为0");

            // 结算 = 0, 结转 = -2000
            int available = 0 + base - 0; // opening=0 + (-2000) - 0 = -2000
            assertTrue(available < 0, "应触发异常结算");
        }

        @Test
        @DisplayName("跨店支出超过充值 → 异常结算")
        void crossExpendExceedsRecharge() {
            records.add(recharge("A", 500));
            records.add(crossExpend("A", "B", 700));

            // 基数 = 500 - 0 - 700 = -200
            int base = 500 - 700;
            assertTrue(base < 0);
        }
    }

    @Nested
    @DisplayName("期初期末结转")
    class BalanceCarryForward {

        @Test
        @DisplayName("正常结算 → 期末余额 = 0")
        void normalClosingIsZero() {
            // 本期流入 10000, 费 1000, 结算 9000
            // closing = 0(期初) + 10000 - 0 - 0 + 0 - 1000(费) - 9000(结算) = 0
            int opening = 0;
            int recharge = 10_000, refund = 0, crossExpend = 0, crossIncome = 0;
            int feeBase = recharge - refund - crossExpend + crossIncome; // 10000
            int fee = feeBase / 10; // 1000
            int settlement = feeBase - fee; // 9000

            int closing = opening + feeBase - fee - settlement;
            assertEquals(0, closing, "正常结算后期末应为0");
        }

        @Test
        @DisplayName("异常结算 → 结转至下期")
        void abnormalCarryForward() {
            // 上期异常结转 -500
            int opening = -500;
            int recharge = 1_000, refund = 0, crossExpend = 0, crossIncome = 0;
            int feeBase = recharge; // 1000 (仅本期)
            int fee = 100; // 1000 × 10%
            int totalAvailable = opening + feeBase - fee; // -500 + 1000 - 100 = 400
            assertTrue(totalAvailable > 0, "含上期结转后应为正");

            int settlement = 400;
            int closing = totalAvailable - settlement; // 0
            assertEquals(0, closing, "抵消上期负结转后期末应为0");
        }
    }

    @Nested
    @DisplayName("边界条件")
    class EdgeCases {

        @Test
        @DisplayName("金额为0 — 不产生结算记录")
        void zeroAmount() {
            // 无任何分账记录 → 跳过结算
            assertTrue(records.isEmpty());
        }

        @Test
        @DisplayName("仅跨店收入 — B站无充值但有服务收入")
        void onlyCrossIncome() {
            records.add(crossIncome("A", "B", 700));
            // B: 基数 = 0 - 0 - 0 + 700 = 700, 费 = 70, 结算 = 630
            assertPlatformFee(0, 0, 0, 700, 70);
            assertSettlementAmount(0, 0, 0, 700, 630);
        }

        @Test
        @DisplayName("大额金额 — 不溢出")
        void largeAmount() {
            int largeAmount = Integer.MAX_VALUE / 100; // ~21,474,836
            records.add(recharge("A", largeAmount));
            int expectedFee = (int) (largeAmount * 0.1);
            assertPlatformFee(largeAmount, 0, 0, 0, expectedFee);
        }
    }

    // ---------- calculation helpers ----------

    private int calcPlatformFee(int recharge, int refund, int crossExpend, int crossIncome) {
        int base = recharge - refund - crossExpend + crossIncome;
        return base > 0 ? (int) Math.floor(base * 0.1) : 0;
    }

    private int calcSettlement(int recharge, int refund, int crossExpend, int crossIncome) {
        int base = recharge - refund - crossExpend + crossIncome;
        int fee = calcPlatformFee(recharge, refund, crossExpend, crossIncome);
        return base - fee;
    }

    private void assertPlatformFee(int recharge, int refund, int crossExpend, int crossIncome, int expectedFee) {
        assertEquals(expectedFee, calcPlatformFee(recharge, refund, crossExpend, crossIncome),
                String.format("平台费计算错误: 充值=%d 退款=%d 跨店支出=%d 跨店收入=%d", recharge, refund, crossExpend, crossIncome));
    }

    private void assertSettlementAmount(int recharge, int refund, int crossExpend, int crossIncome, int expected) {
        assertEquals(expected, calcSettlement(recharge, refund, crossExpend, crossIncome),
                String.format("结算金额计算错误: 充值=%d 退款=%d 跨店支出=%d 跨店收入=%d", recharge, refund, crossExpend, crossIncome));
    }
}
