package com.starry.admin.modules.withdraw.service.impl;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.withdraw.entity.EarningsLineEntity;
import com.starry.admin.modules.withdraw.mapper.EarningsLineMapper;
import com.starry.admin.modules.withdraw.service.IFreezePolicyService;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class EarningsServiceImplTest {

    @InjectMocks
    private EarningsServiceImpl earningsService;

    @Mock
    private EarningsLineMapper baseMapper;

    @Mock
    private IFreezePolicyService freezePolicyService;

    private PlayOrderInfoEntity baselineOrder() {
        PlayOrderInfoEntity order = new PlayOrderInfoEntity();
        order.setId("order-001");
        order.setTenantId("tenant-001");
        order.setAcceptBy("clerk-001");
        order.setOrderEndTime(LocalDateTime.now());
        order.setOrderSettlementState("0");
        return order;
    }

    @Test
    void createFromOrder_usesOrderAmountWhenEstimatedMissing_evenIfZero() {
        PlayOrderInfoEntity order = baselineOrder();
        order.setEstimatedRevenue(null);
        order.setOrderMoney(BigDecimal.ZERO);

        when(freezePolicyService.resolveFreezeHours("tenant-001", "clerk-001")).thenReturn(0);
        when(baseMapper.insert(any())).thenReturn(1);

        earningsService.createFromOrder(order);

        ArgumentCaptor<EarningsLineEntity> captor = ArgumentCaptor.forClass(EarningsLineEntity.class);
        verify(baseMapper).insert(captor.capture());
        assertEquals(BigDecimal.ZERO, captor.getValue().getAmount());
    }

    @Test
    void createFromOrder_skipsWhenAmountNegative() {
        PlayOrderInfoEntity order = baselineOrder();
        order.setEstimatedRevenue(BigDecimal.valueOf(-10));
        order.setOrderMoney(BigDecimal.valueOf(20));

        earningsService.createFromOrder(order);

        verify(baseMapper, never()).insert(any());
    }

    @Test
    void createFromOrder_skipsBlindBoxPurchaseOrders() {
        PlayOrderInfoEntity order = baselineOrder();
        order.setEstimatedRevenue(BigDecimal.valueOf(66));
        order.setOrderType(OrderConstant.OrderType.BLIND_BOX_PURCHASE.getCode());

        earningsService.createFromOrder(order);

        verify(baseMapper, never()).insert(any());
    }

    @Test
    void createCounterLine_throwsWhenNoReferencePresent() {
        when(baseMapper.selectList(any())).thenReturn(Collections.emptyList());

        assertThrows(IllegalStateException.class, () ->
                earningsService.createCounterLine("order-neg", "tenant-t", "clerk-c", BigDecimal.TEN, "admin"));
    }

    @Test
    void createCounterLine_persistsNegativeAvailableLine() {
        EarningsLineEntity reference = new EarningsLineEntity();
        reference.setAmount(new BigDecimal("88.00"));
        reference.setUnlockTime(LocalDateTime.now().minusHours(1));
        reference.setStatus("available");
        when(baseMapper.selectList(any())).thenReturn(Collections.singletonList(reference));
        when(baseMapper.insert(any())).thenReturn(1);

        earningsService.createCounterLine("order-neg", "tenant-t", "clerk-c", BigDecimal.valueOf(88), "admin");

        ArgumentCaptor<EarningsLineEntity> captor = ArgumentCaptor.forClass(EarningsLineEntity.class);
        verify(baseMapper).insert(captor.capture());
        EarningsLineEntity saved = captor.getValue();
        assertEquals(new BigDecimal("-88"), saved.getAmount());
        assertEquals("clerk-c", saved.getClerkId());
        assertEquals("available", saved.getStatus());
    }

    @Test
    void createCounterLine_inheritsFrozenUnlockScheduleFromReference() {
        LocalDateTime unlockAt = LocalDateTime.now().plusDays(1).withNano(0);
        EarningsLineEntity reference = new EarningsLineEntity();
        reference.setAmount(new BigDecimal("150.00"));
        reference.setUnlockTime(unlockAt);
        reference.setStatus("frozen");
        when(baseMapper.selectList(any())).thenReturn(Collections.singletonList(reference));
        when(baseMapper.insert(any())).thenReturn(1);

        earningsService.createCounterLine("order-neg", "tenant-t", "clerk-c", BigDecimal.valueOf(40), "admin");

        ArgumentCaptor<EarningsLineEntity> captor = ArgumentCaptor.forClass(EarningsLineEntity.class);
        verify(baseMapper).insert(captor.capture());
        EarningsLineEntity saved = captor.getValue();
        assertEquals("frozen", saved.getStatus());
        assertEquals(unlockAt, saved.getUnlockTime());
    }

    @Test
    void createCounterLine_unlockedReferenceProducesAvailableCounter() {
        LocalDateTime unlockAt = LocalDateTime.now().minusHours(3).withNano(0);
        EarningsLineEntity reference = new EarningsLineEntity();
        reference.setAmount(new BigDecimal("95.00"));
        reference.setUnlockTime(unlockAt);
        reference.setStatus("available");
        when(baseMapper.selectList(any())).thenReturn(Collections.singletonList(reference));
        when(baseMapper.insert(any())).thenReturn(1);

        earningsService.createCounterLine("order-neg", "tenant-t", "clerk-c", BigDecimal.valueOf(55), "admin");

        ArgumentCaptor<EarningsLineEntity> captor = ArgumentCaptor.forClass(EarningsLineEntity.class);
        verify(baseMapper).insert(captor.capture());
        EarningsLineEntity saved = captor.getValue();
        assertEquals("available", saved.getStatus());
        LocalDateTime now = LocalDateTime.now();
        assertTrue(saved.getUnlockTime().isAfter(unlockAt));
        assertTrue(!saved.getUnlockTime().isAfter(now.plusSeconds(1)));
    }

    @Test
    void findWithdrawable_returnsEmptyWhenCounterLinesCreateDebt() {
        LocalDateTime now = LocalDateTime.now();
        List<EarningsLineEntity> lines = Arrays.asList(
                line("neg", new BigDecimal("-60")),
                line("pos", new BigDecimal("40")));
        when(baseMapper.selectWithdrawableLines("clerk-001", now)).thenReturn(lines);

        List<EarningsLineEntity> picked = earningsService.findWithdrawable("clerk-001", BigDecimal.valueOf(30), now);

        assertEquals(0, picked.size(), "净额不足时不应允许提现");
    }

    @Test
    void findWithdrawable_allowsWithdrawalAfterPositiveLinesCoverDebt() {
        LocalDateTime now = LocalDateTime.now();
        List<EarningsLineEntity> lines = Arrays.asList(
                line("neg", new BigDecimal("-60")),
                line("first", new BigDecimal("40")),
                line("second", new BigDecimal("150")));
        when(baseMapper.selectWithdrawableLines("clerk-001", now)).thenReturn(lines);

        List<EarningsLineEntity> picked = earningsService.findWithdrawable("clerk-001", BigDecimal.valueOf(70), now);

        assertEquals(3, picked.size());
        assertEquals("second", picked.get(2).getId());
    }

    @Test
    void findWithdrawable_handlesMixedPositiveAndNegativeSequences() {
        LocalDateTime now = LocalDateTime.now();
        List<EarningsLineEntity> lines = Arrays.asList(
                line("neg-30", new BigDecimal("-30")),
                line("pos-20", new BigDecimal("20")),
                line("pos-50", new BigDecimal("50")),
                line("neg-10", new BigDecimal("-10")),
                line("pos-40", new BigDecimal("40")),
                line("pos-60", new BigDecimal("60")),
                line("pos-15", new BigDecimal("15")),
                line("pos-25", new BigDecimal("25")),
                line("neg-5", new BigDecimal("-5")),
                line("pos-100", new BigDecimal("100")));

        when(baseMapper.selectWithdrawableLines("clerk-mix", now)).thenReturn(lines, lines, lines);

        List<EarningsLineEntity> partial = earningsService.findWithdrawable("clerk-mix", new BigDecimal("35"), now);
        assertEquals(Arrays.asList("neg-30", "pos-20", "pos-50"), ids(partial));

        List<EarningsLineEntity> mid = earningsService.findWithdrawable("clerk-mix", new BigDecimal("90"), now);
        assertEquals(Arrays.asList("neg-30", "pos-20", "pos-50", "neg-10", "pos-40", "pos-60"), ids(mid));

        List<EarningsLineEntity> full = earningsService.findWithdrawable("clerk-mix", new BigDecimal("265"), now);
        assertEquals(lines.size(), full.size());
        assertEquals("pos-100", full.get(full.size() - 1).getId());
    }

    private EarningsLineEntity line(String id, BigDecimal amount) {
        EarningsLineEntity entity = new EarningsLineEntity();
        entity.setId(id);
        entity.setAmount(amount);
        entity.setStatus("available");
        return entity;
    }

    private List<String> ids(List<EarningsLineEntity> entities) {
        return entities.stream().map(EarningsLineEntity::getId).collect(java.util.stream.Collectors.toList());
    }
}
