package com.mosquito.project.service;

import com.mosquito.project.persistence.entity.RewardJobEntity;
import com.mosquito.project.persistence.repository.RewardJobRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
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;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.lenient;

@ExtendWith(MockitoExtension.class)
@DisplayName("数据库奖励队列测试")
class DbRewardQueueTest {

    @Mock
    private RewardJobRepository repository;

    @InjectMocks
    private DbRewardQueue rewardQueue;

    private final String TRACKING_ID = "track-123-456";
    private final String EXTERNAL_USER_ID = "user-789";
    private final String PAYLOAD_JSON = "{\"amount\":100,\"type\":\"reward\"}";

    @BeforeEach
    void setUp() {
        lenient().when(repository.save(any(RewardJobEntity.class))).thenAnswer(invocation -> {
            RewardJobEntity entity = invocation.getArgument(0);
            if (entity.getId() == null) {
                entity.setId(1L);
            }
            return entity;
        });
    }

    @Test
    @DisplayName("入队奖励 - 应正确创建并保存任务实体")
    void shouldCreateAndSaveJob_whenEnqueueReward() {
        // Given
        ArgumentCaptor<RewardJobEntity> captor = ArgumentCaptor.forClass(RewardJobEntity.class);

        // When
        rewardQueue.enqueueReward(TRACKING_ID, EXTERNAL_USER_ID, PAYLOAD_JSON);

        // Then
        then(repository).should().save(captor.capture());
        RewardJobEntity savedEntity = captor.getValue();

        assertThat(savedEntity.getTrackingId()).isEqualTo(TRACKING_ID);
        assertThat(savedEntity.getExternalUserId()).isEqualTo(EXTERNAL_USER_ID);
        assertThat(savedEntity.getPayload()).isEqualTo(PAYLOAD_JSON);
        assertThat(savedEntity.getStatus()).isEqualTo("pending");
        assertThat(savedEntity.getRetryCount()).isEqualTo(0);
        assertThat(savedEntity.getCreatedAt()).isNotNull();
        assertThat(savedEntity.getUpdatedAt()).isNotNull();
        assertThat(savedEntity.getNextRunAt()).isNotNull();
    }

    @Test
    @DisplayName("入队奖励 - 应设置当前时间戳")
    void shouldSetCurrentTimestamps_whenEnqueueReward() {
        // Given
        OffsetDateTime before = OffsetDateTime.now(ZoneOffset.UTC);
        ArgumentCaptor<RewardJobEntity> captor = ArgumentCaptor.forClass(RewardJobEntity.class);

        // When
        rewardQueue.enqueueReward(TRACKING_ID, EXTERNAL_USER_ID, PAYLOAD_JSON);

        // Then
        then(repository).should().save(captor.capture());
        RewardJobEntity savedEntity = captor.getValue();

        OffsetDateTime after = OffsetDateTime.now(ZoneOffset.UTC);
        assertThat(savedEntity.getCreatedAt()).isBetween(before, after);
        assertThat(savedEntity.getUpdatedAt()).isBetween(before, after);
        assertThat(savedEntity.getNextRunAt()).isBetween(before, after);
    }

    @Test
    @DisplayName("入队奖励 - 应处理空externalUserId")
    void shouldHandleNullExternalUserId_whenEnqueueReward() {
        // Given
        ArgumentCaptor<RewardJobEntity> captor = ArgumentCaptor.forClass(RewardJobEntity.class);

        // When
        rewardQueue.enqueueReward(TRACKING_ID, null, PAYLOAD_JSON);

        // Then
        then(repository).should().save(captor.capture());
        RewardJobEntity savedEntity = captor.getValue();

        assertThat(savedEntity.getExternalUserId()).isNull();
    }

    @Test
    @DisplayName("入队奖励 - 应处理空payload")
    void shouldHandleNullPayload_whenEnqueueReward() {
        // Given
        ArgumentCaptor<RewardJobEntity> captor = ArgumentCaptor.forClass(RewardJobEntity.class);

        // When
        rewardQueue.enqueueReward(TRACKING_ID, EXTERNAL_USER_ID, null);

        // Then
        then(repository).should().save(captor.capture());
        RewardJobEntity savedEntity = captor.getValue();

        assertThat(savedEntity.getPayload()).isNull();
    }

    @Test
    @DisplayName("入队奖励 - 应处理空trackingId")
    void shouldHandleNullTrackingId_whenEnqueueReward() {
        // Given
        ArgumentCaptor<RewardJobEntity> captor = ArgumentCaptor.forClass(RewardJobEntity.class);

        // When
        rewardQueue.enqueueReward(null, EXTERNAL_USER_ID, PAYLOAD_JSON);

        // Then
        then(repository).should().save(captor.capture());
        RewardJobEntity savedEntity = captor.getValue();

        assertThat(savedEntity.getTrackingId()).isNull();
    }

    @Test
    @DisplayName("入队奖励 - 应设置默认状态为pending")
    void shouldSetDefaultStatusAsPending_whenEnqueueReward() {
        // Given
        ArgumentCaptor<RewardJobEntity> captor = ArgumentCaptor.forClass(RewardJobEntity.class);

        // When
        rewardQueue.enqueueReward(TRACKING_ID, EXTERNAL_USER_ID, PAYLOAD_JSON);

        // Then
        then(repository).should().save(captor.capture());
        RewardJobEntity savedEntity = captor.getValue();

        assertThat(savedEntity.getStatus()).isEqualTo("pending");
    }

    @Test
    @DisplayName("入队奖励 - 应设置重试计数为0")
    void shouldSetRetryCountAsZero_whenEnqueueReward() {
        // Given
        ArgumentCaptor<RewardJobEntity> captor = ArgumentCaptor.forClass(RewardJobEntity.class);

        // When
        rewardQueue.enqueueReward(TRACKING_ID, EXTERNAL_USER_ID, PAYLOAD_JSON);

        // Then
        then(repository).should().save(captor.capture());
        RewardJobEntity savedEntity = captor.getValue();

        assertThat(savedEntity.getRetryCount()).isEqualTo(0);
    }

    @Test
    @DisplayName("入队奖励 - 应处理大型payload")
    void shouldHandleLargePayload_whenEnqueueReward() {
        // Given
        String largePayload = "{\"data\":\"" + "a".repeat(10000) + "\"}";
        ArgumentCaptor<RewardJobEntity> captor = ArgumentCaptor.forClass(RewardJobEntity.class);

        // When
        rewardQueue.enqueueReward(TRACKING_ID, EXTERNAL_USER_ID, largePayload);

        // Then
        then(repository).should().save(captor.capture());
        RewardJobEntity savedEntity = captor.getValue();

        assertThat(savedEntity.getPayload()).hasSize(largePayload.length());
    }
}
