package com.starry.admin.modules.blindbox.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
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.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.starry.admin.common.exception.CustomException;
import com.starry.admin.modules.blindbox.mapper.BlindBoxPoolMapper;
import com.starry.admin.modules.blindbox.module.dto.BlindBoxGiftOption;
import com.starry.admin.modules.blindbox.module.dto.BlindBoxPoolImportRow;
import com.starry.admin.modules.blindbox.module.dto.BlindBoxPoolUpsertRequest;
import com.starry.admin.modules.blindbox.module.dto.BlindBoxPoolView;
import com.starry.admin.modules.blindbox.module.entity.BlindBoxConfigEntity;
import com.starry.admin.modules.blindbox.module.entity.BlindBoxPoolEntity;
import com.starry.admin.modules.shop.mapper.PlayGiftInfoMapper;
import com.starry.admin.modules.shop.module.entity.PlayGiftInfoEntity;
import com.starry.admin.utils.SecurityUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
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 BlindBoxPoolAdminServiceTest {

    @Mock
    private BlindBoxPoolMapper blindBoxPoolMapper;

    @Mock
    private PlayGiftInfoMapper playGiftInfoMapper;

    @Mock
    private BlindBoxConfigService blindBoxConfigService;

    @InjectMocks
    private BlindBoxPoolAdminService blindBoxPoolAdminService;

    @BeforeEach
    void setupContext() {
        SecurityUtils.setTenantId("tenant-1");
    }

    @Test
    void shouldListPoolsWithGiftNames() {
        BlindBoxPoolEntity entity = new BlindBoxPoolEntity();
        entity.setId(10L);
        entity.setTenantId("tenant-1");
        entity.setBlindBoxId("blind-1");
        entity.setRewardGiftId("gift-2");
        entity.setRewardPrice(BigDecimal.valueOf(9.9));
        entity.setWeight(50);
        entity.setRemainingStock(100);
        entity.setValidFrom(LocalDateTime.of(2024, 8, 1, 0, 0));
        entity.setStatus(1);

        when(blindBoxPoolMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(Collections.singletonList(entity));
        BlindBoxConfigEntity config = new BlindBoxConfigEntity();
        config.setId("blind-1");
        config.setTenantId("tenant-1");
        config.setName("幸运盲盒");
        when(blindBoxConfigService.requireById("blind-1")).thenReturn(config);
        PlayGiftInfoEntity reward = new PlayGiftInfoEntity();
        reward.setId("gift-2");
        reward.setName("超值娃娃");
        when(playGiftInfoMapper.selectBatchIds(any(Collection.class)))
                .thenAnswer(invocation -> Arrays.asList(reward));

        List<BlindBoxPoolView> views = blindBoxPoolAdminService.list("blind-1");

        assertEquals(1, views.size());
        BlindBoxPoolView view = views.get(0);
        assertEquals("幸运盲盒", view.getBlindBoxName());
        assertEquals("超值娃娃", view.getRewardGiftName());
        assertEquals(50, view.getWeight());
    }

    @Test
    void shouldReplacePoolAndInsertRows() {
        BlindBoxConfigEntity config = new BlindBoxConfigEntity();
        config.setId("blind-1");
        config.setTenantId("tenant-1");
        when(blindBoxConfigService.requireById("blind-1")).thenReturn(config);

        PlayGiftInfoEntity reward = new PlayGiftInfoEntity();
        reward.setId("gift-2");
        reward.setName("超值娃娃");
        reward.setType("1");
        reward.setPrice(BigDecimal.valueOf(9.9));
        when(playGiftInfoMapper.selectList(any(LambdaQueryWrapper.class)))
                .thenReturn(Collections.singletonList(reward));

        when(blindBoxPoolMapper.delete(any(LambdaQueryWrapper.class))).thenReturn(1);
        AtomicInteger insertCount = new AtomicInteger();
        when(blindBoxPoolMapper.insert(any(BlindBoxPoolEntity.class)))
                .thenAnswer(invocation -> {
                    insertCount.incrementAndGet();
                    return 1;
                });

        BlindBoxPoolImportRow row = new BlindBoxPoolImportRow();
        row.setRewardGiftName("超值娃娃");
        row.setWeight(80);
        row.setRemainingStock(10);
        row.setStatus(1);

        blindBoxPoolAdminService.replacePool("blind-1", Collections.singletonList(row));

        assertEquals(1, insertCount.get());
        ArgumentCaptor<BlindBoxPoolEntity> captor = ArgumentCaptor.forClass(BlindBoxPoolEntity.class);
        verify(blindBoxPoolMapper).insert(captor.capture());
        BlindBoxPoolEntity saved = captor.getValue();
        assertEquals("gift-2", saved.getRewardGiftId());
        assertEquals(Integer.valueOf(80), saved.getWeight());
        assertEquals(BigDecimal.valueOf(9.9), saved.getRewardPrice());
    }

    @Test
    void shouldThrowWhenRewardGiftMissing() {
        BlindBoxConfigEntity config = new BlindBoxConfigEntity();
        config.setId("blind-1");
        config.setTenantId("tenant-1");
        when(blindBoxConfigService.requireById("blind-1")).thenReturn(config);
        when(playGiftInfoMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(Collections.emptyList());

        BlindBoxPoolImportRow row = new BlindBoxPoolImportRow();
        row.setRewardGiftName("未知礼物");
        row.setWeight(10);

        assertThrows(CustomException.class,
                () -> blindBoxPoolAdminService.replacePool("blind-1", Collections.singletonList(row)));
    }

    @Test
    void shouldThrowForInvalidWeight() {
        BlindBoxConfigEntity config = new BlindBoxConfigEntity();
        config.setId("blind-1");
        config.setTenantId("tenant-1");
        when(blindBoxConfigService.requireById("blind-1")).thenReturn(config);
        PlayGiftInfoEntity reward = new PlayGiftInfoEntity();
        reward.setId("gift-2");
        reward.setName("超值娃娃");
        reward.setType("1");
        reward.setPrice(BigDecimal.ONE);
        when(playGiftInfoMapper.selectList(any(LambdaQueryWrapper.class)))
                .thenReturn(Collections.singletonList(reward));

        BlindBoxPoolImportRow row = new BlindBoxPoolImportRow();
        row.setRewardGiftName("超值娃娃");
        row.setWeight(0);

        assertThrows(CustomException.class,
                () -> blindBoxPoolAdminService.replacePool("blind-1", Collections.singletonList(row)));
    }

    @Test
    void shouldListGiftOptions() {
        PlayGiftInfoEntity gift = new PlayGiftInfoEntity();
        gift.setId("gift-1");
        gift.setTenantId("tenant-1");
        gift.setHistory("0");
        gift.setState("0");
        gift.setType("1");
        gift.setPrice(BigDecimal.valueOf(19.9));
        gift.setName("超值娃娃");
        gift.setUrl("https://image");
        when(playGiftInfoMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(Collections.singletonList(gift));

        List<BlindBoxGiftOption> options = blindBoxPoolAdminService.listGiftOptions("娃");

        assertEquals(1, options.size());
        assertEquals("gift-1", options.get(0).getId());
        assertEquals("超值娃娃", options.get(0).getName());
    }

    @Test
    void shouldOverwriteRemainingStockOnReimport() {
        BlindBoxConfigEntity config = new BlindBoxConfigEntity();
        config.setId("blind-1");
        config.setTenantId("tenant-1");
        when(blindBoxConfigService.requireById("blind-1")).thenReturn(config);

        PlayGiftInfoEntity reward = new PlayGiftInfoEntity();
        reward.setId("gift-2");
        reward.setName("超值娃娃");
        reward.setType("1");
        reward.setPrice(BigDecimal.valueOf(9.9));
        when(playGiftInfoMapper.selectList(any(LambdaQueryWrapper.class)))
                .thenReturn(Collections.singletonList(reward));
        when(blindBoxPoolMapper.delete(any(LambdaQueryWrapper.class))).thenReturn(1);

        List<BlindBoxPoolEntity> inserted = new ArrayList<>();
        when(blindBoxPoolMapper.insert(any(BlindBoxPoolEntity.class))).thenAnswer(invocation -> {
            BlindBoxPoolEntity entity = invocation.getArgument(0);
            BlindBoxPoolEntity snapshot = new BlindBoxPoolEntity();
            snapshot.setBlindBoxId(entity.getBlindBoxId());
            snapshot.setRewardGiftId(entity.getRewardGiftId());
            snapshot.setRemainingStock(entity.getRemainingStock());
            inserted.add(snapshot);
            return 1;
        });

        BlindBoxPoolImportRow first = new BlindBoxPoolImportRow();
        first.setRewardGiftName("超值娃娃");
        first.setWeight(50);
        first.setRemainingStock(5);
        first.setStatus(1);

        BlindBoxPoolImportRow second = new BlindBoxPoolImportRow();
        second.setRewardGiftName("超值娃娃");
        second.setWeight(60);
        second.setRemainingStock(1);
        second.setStatus(1);

        blindBoxPoolAdminService.replacePool("blind-1", Collections.singletonList(first));
        blindBoxPoolAdminService.replacePool("blind-1", Collections.singletonList(second));

        assertEquals(2, inserted.size());
        assertEquals(Integer.valueOf(5), inserted.get(0).getRemainingStock());
        assertEquals(Integer.valueOf(1), inserted.get(1).getRemainingStock());
    }

    @Test
    void shouldKeepUnlimitedStockWhenRemainingStockBlank() {
        BlindBoxConfigEntity config = new BlindBoxConfigEntity();
        config.setId("blind-1");
        config.setTenantId("tenant-1");
        when(blindBoxConfigService.requireById("blind-1")).thenReturn(config);

        PlayGiftInfoEntity reward = new PlayGiftInfoEntity();
        reward.setId("gift-2");
        reward.setName("超值娃娃");
        reward.setType("1");
        reward.setPrice(BigDecimal.valueOf(9.9));
        when(playGiftInfoMapper.selectList(any(LambdaQueryWrapper.class)))
                .thenReturn(Collections.singletonList(reward));
        when(blindBoxPoolMapper.delete(any(LambdaQueryWrapper.class))).thenReturn(1);

        ArgumentCaptor<BlindBoxPoolEntity> captor = ArgumentCaptor.forClass(BlindBoxPoolEntity.class);
        when(blindBoxPoolMapper.insert(any(BlindBoxPoolEntity.class))).thenReturn(1);

        BlindBoxPoolImportRow importRow = new BlindBoxPoolImportRow();
        importRow.setRewardGiftName("超值娃娃");
        importRow.setWeight(30);
        importRow.setStatus(1);

        blindBoxPoolAdminService.replacePool("blind-1", Collections.singletonList(importRow));

        verify(blindBoxPoolMapper).insert(captor.capture());
        BlindBoxPoolEntity saved = captor.getValue();
        assertNull(saved.getRemainingStock());
    }

    @Test
    void shouldCreatePoolEntry() {
        BlindBoxConfigEntity config = new BlindBoxConfigEntity();
        config.setId("blind-1");
        config.setTenantId("tenant-1");
        config.setName("幸运盲盒");
        when(blindBoxConfigService.requireById("blind-1")).thenReturn(config);

        PlayGiftInfoEntity gift = new PlayGiftInfoEntity();
        gift.setId("gift-2");
        gift.setTenantId("tenant-1");
        gift.setHistory("0");
        gift.setState("0");
        gift.setType("1");
        gift.setPrice(BigDecimal.valueOf(9.9));
        gift.setName("超值娃娃");
        when(playGiftInfoMapper.selectById("gift-2")).thenReturn(gift);

        when(blindBoxPoolMapper.insert(any(BlindBoxPoolEntity.class))).thenAnswer(invocation -> {
            BlindBoxPoolEntity entity = invocation.getArgument(0);
            entity.setId(100L);
            return 1;
        });

        BlindBoxPoolUpsertRequest request = new BlindBoxPoolUpsertRequest();
        request.setBlindBoxId("blind-1");
        request.setRewardGiftId("gift-2");
        request.setRewardPrice(BigDecimal.valueOf(12.5));
        request.setWeight(80);
        request.setRemainingStock(5);
        request.setStatus(1);
        request.setValidFrom(LocalDateTime.of(2024, 8, 1, 0, 0));
        request.setValidTo(LocalDateTime.of(2024, 8, 31, 23, 59, 59));

        BlindBoxPoolView view = blindBoxPoolAdminService.create("blind-1", request);

        assertEquals("gift-2", view.getRewardGiftId());
        assertEquals(Integer.valueOf(80), view.getWeight());
        verify(blindBoxPoolMapper).insert(any(BlindBoxPoolEntity.class));
    }

    @Test
    void shouldUpdatePoolEntry() {
        BlindBoxConfigEntity config = new BlindBoxConfigEntity();
        config.setId("blind-1");
        config.setTenantId("tenant-1");
        config.setName("幸运盲盒");
        when(blindBoxConfigService.requireById("blind-1")).thenReturn(config);

        PlayGiftInfoEntity gift = new PlayGiftInfoEntity();
        gift.setId("gift-3");
        gift.setTenantId("tenant-1");
        gift.setHistory("0");
        gift.setState("0");
        gift.setType("1");
        gift.setPrice(BigDecimal.valueOf(19.9));
        gift.setName("超级公仔");
        when(playGiftInfoMapper.selectById("gift-3")).thenReturn(gift);

        BlindBoxPoolEntity existing = new BlindBoxPoolEntity();
        existing.setId(200L);
        existing.setTenantId("tenant-1");
        existing.setBlindBoxId("blind-1");
        existing.setRewardGiftId("gift-1");
        existing.setStatus(1);
        when(blindBoxPoolMapper.selectById(200L)).thenReturn(existing);
        when(blindBoxPoolMapper.updateById(any(BlindBoxPoolEntity.class))).thenReturn(1);

        BlindBoxPoolUpsertRequest request = new BlindBoxPoolUpsertRequest();
        request.setBlindBoxId("blind-1");
        request.setRewardGiftId("gift-3");
        request.setWeight(60);
        request.setRemainingStock(null);
        request.setStatus(0);
        request.setRewardPrice(null);
        request.setValidFrom(LocalDateTime.of(2024, 9, 1, 0, 0));
        request.setValidTo(LocalDateTime.of(2024, 9, 30, 23, 59, 59));

        BlindBoxPoolView view = blindBoxPoolAdminService.update(200L, request);

        assertEquals("gift-3", existing.getRewardGiftId());
        assertEquals(Integer.valueOf(60), existing.getWeight());
        assertEquals(Integer.valueOf(0), existing.getStatus());
        assertEquals("超级公仔", view.getRewardGiftName());
        verify(blindBoxPoolMapper).updateById(existing);
    }

    @Test
    void shouldAllowUpdateWhenGiftInactiveOrHistorical() {
        BlindBoxConfigEntity config = new BlindBoxConfigEntity();
        config.setId("blind-1");
        config.setTenantId("tenant-1");
        when(blindBoxConfigService.requireById("blind-1")).thenReturn(config);

        PlayGiftInfoEntity inactiveGift = new PlayGiftInfoEntity();
        inactiveGift.setId("gift-offline");
        inactiveGift.setTenantId("tenant-1");
        inactiveGift.setHistory("1");
        inactiveGift.setState("1");
        inactiveGift.setType("1");
        inactiveGift.setPrice(BigDecimal.valueOf(66.6));
        inactiveGift.setName("下架礼物");
        when(playGiftInfoMapper.selectById("gift-offline")).thenReturn(inactiveGift);

        BlindBoxPoolEntity existing = new BlindBoxPoolEntity();
        existing.setId(500L);
        existing.setTenantId("tenant-1");
        existing.setBlindBoxId("blind-1");
        existing.setRewardGiftId("gift-on");
        existing.setStatus(1);
        when(blindBoxPoolMapper.selectById(500L)).thenReturn(existing);
        when(blindBoxPoolMapper.updateById(existing)).thenReturn(1);

        BlindBoxPoolUpsertRequest request = new BlindBoxPoolUpsertRequest();
        request.setBlindBoxId("blind-1");
        request.setRewardGiftId("gift-offline");
        request.setWeight(10);
        request.setStatus(1);

        blindBoxPoolAdminService.update(500L, request);

        verify(blindBoxPoolMapper).updateById(existing);
        assertEquals("gift-offline", existing.getRewardGiftId());
    }

    @Test
    void shouldRejectCreateWhenGiftInactive() {
        BlindBoxConfigEntity config = new BlindBoxConfigEntity();
        config.setId("blind-1");
        config.setTenantId("tenant-1");
        when(blindBoxConfigService.requireById("blind-1")).thenReturn(config);

        PlayGiftInfoEntity inactiveGift = new PlayGiftInfoEntity();
        inactiveGift.setId("gift-off");
        inactiveGift.setTenantId("tenant-1");
        inactiveGift.setHistory("1");
        inactiveGift.setState("1");
        inactiveGift.setType("1");
        when(playGiftInfoMapper.selectById("gift-off")).thenReturn(inactiveGift);

        BlindBoxPoolUpsertRequest request = new BlindBoxPoolUpsertRequest();
        request.setBlindBoxId("blind-1");
        request.setRewardGiftId("gift-off");
        request.setWeight(10);

        CustomException ex = assertThrows(CustomException.class,
                () -> blindBoxPoolAdminService.create("blind-1", request));
        assertTrue(ex.getMessage().contains("中奖礼物不存在或已下架"));
        verify(blindBoxPoolMapper, times(0)).insert(any());
    }
}
