package cn.novalon.gym.manage.checkin;

import cn.novalon.gym.manage.checkIn.config.QRCodeConfig;
import cn.novalon.gym.manage.checkIn.entity.SignInRecord;
import cn.novalon.gym.manage.checkIn.repository.SignInRecordRepository;
import cn.novalon.gym.manage.groupcourse.service.IGroupCourseBookingService;
import cn.novalon.gym.manage.checkIn.service.impl.CheckServiceImpl;
import cn.novalon.gym.manage.checkIn.vo.QRCodeVo;
import cn.novalon.gym.manage.checkIn.vo.SignInRecordVO;
import cn.novalon.gym.manage.checkIn.vo.SignInStatsVO;
import cn.novalon.gym.manage.common.constant.RedisKeyConstants;
import cn.novalon.gym.manage.common.util.RedisUtil;
import cn.novalon.gym.manage.member.entity.MemberCard;
import cn.novalon.gym.manage.member.entity.MemberCardRecord;
import cn.novalon.gym.manage.member.repository.MemberCardRecordRepository;
import cn.novalon.gym.manage.member.repository.MemberCardRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.HashMap;
import java.util.Map;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

/**
 * 签到模块接口测试类
 * 测试模块三（gym-checkIn）的所有接口
 */
class CheckInModuleTest {

    @Mock
    private QRCodeConfig qrCodeConfig;

    @Mock
    private RedisUtil redisUtil;

    @Mock
    private MemberCardRecordRepository memberCardRecordRepository;

    @Mock
    private MemberCardRepository memberCardRepository;

    @Mock
    private SignInRecordRepository signInRecordRepository;

    @Mock
    private IGroupCourseBookingService groupCourseBookingService;

    @Mock
    private MemberCard mockMemberCard;

    @Mock
    private SignInRecord mockSignInRecord;

    @Mock
    private MemberCardRecord mockMemberCardRecord;

    private CheckServiceImpl checkService;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        checkService = new CheckServiceImpl(qrCodeConfig, redisUtil, memberCardRecordRepository, 
                memberCardRepository, signInRecordRepository, groupCourseBookingService);

        when(mockMemberCard.getId()).thenReturn(1L);
        when(mockMemberCard.getMemberCardType()).thenReturn("TIME_CARD");

        when(mockSignInRecord.getId()).thenReturn(1L);
        when(mockSignInRecord.getMemberId()).thenReturn(1L);
        when(mockSignInRecord.getMemberCardId()).thenReturn(1L);
        when(mockSignInRecord.getSignInTime()).thenReturn(LocalDateTime.now());
        when(mockSignInRecord.getSignInType()).thenReturn("QR_CODE");
        when(mockSignInRecord.getSignInStatus()).thenReturn("SUCCESS");
        when(mockSignInRecord.getSource()).thenReturn("MINI_PROGRAM");

        when(mockMemberCardRecord.getMemberCardRecordId()).thenReturn(1L);
        when(mockMemberCardRecord.getMemberCardId()).thenReturn(1L);
        when(mockMemberCardRecord.getRemainingTimes()).thenReturn(10);
        when(mockMemberCardRecord.getRemainingAmount()).thenReturn(100.0);
        when(mockMemberCardRecord.getExpireTime()).thenReturn(LocalDateTime.now().plusDays(30));
        when(mockMemberCardRecord.getStatus()).thenReturn(cn.novalon.gym.manage.member.enums.MemberCardRecordStatus.ACTIVE);
    }

    @Test
    @DisplayName("测试1: 获取二维码 - getQRCode")
    void testGetQRCode() {
        when(memberCardRecordRepository.findActiveCardsByMemberId(1L))
                .thenReturn(Flux.just(mockMemberCardRecord));
        when(redisUtil.setWithExpire(any(String.class), any(Map.class), any(Long.class)))
                .thenReturn(Mono.just(true));

        Mono<QRCodeVo> result = checkService.getQRCode(1L);

        StepVerifier.create(result)
                .expectNextMatches(qrCodeVo -> {
                    org.junit.jupiter.api.Assertions.assertNotNull(qrCodeVo);
                    org.junit.jupiter.api.Assertions.assertNotNull(qrCodeVo.getQrContent());
                    return true;
                })
                .verifyComplete();
    }

    @Test
    @DisplayName("测试2: 签到 - checkIn")
    void testCheckIn() {
        Long memberId = 1L;
        Map<String, Object> qrData = new HashMap<>();
        qrData.put("qrContent", "test-qr-content");
        qrData.put("memberId", memberId);
        qrData.put("memberCardRecordId", 1L);
        qrData.put("isUsed", false);
        qrData.put("expireTime", System.currentTimeMillis() + 3600000);
        
        String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();
        
        when(redisUtil.get(eq(key))).thenReturn(Mono.just(qrData));
        when(memberCardRecordRepository.findById(1L)).thenReturn(Mono.just(mockMemberCardRecord));
        when(memberCardRepository.findByMemberCardIdAndDeletedAtIsNull(1L)).thenReturn(Mono.just(mockMemberCard));
        when(signInRecordRepository.save(any(SignInRecord.class))).thenReturn(Mono.just(mockSignInRecord));
        when(redisUtil.set(any(String.class), any(Map.class))).thenReturn(Mono.just(true));
        when(groupCourseBookingService.getBookingsByMemberId(memberId)).thenReturn(Flux.empty());
        when(signInRecordRepository.findByMemberIdAndDate(eq(memberId), any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Mono.empty());

        Mono<String> result = checkService.checkIn(memberId, "test-qr-content");

        StepVerifier.create(result)
                .expectNextMatches(response -> response.contains("签到成功"))
                .verifyComplete();
    }

    @Test
    @DisplayName("测试3: 查询签到记录列表 - getSignInRecords")
    void testGetSignInRecords() {
        when(signInRecordRepository.findByMemberIdAndTimeRange(
                eq(1L), any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Flux.just(mockSignInRecord));

        Flux<SignInRecordVO> result = checkService.getSignInRecords(1L, 
                LocalDate.now().minusDays(30), LocalDate.now());

        StepVerifier.create(result)
                .expectNextCount(1)
                .verifyComplete();
    }

    @Test
    @DisplayName("测试4: 查询单条签到记录 - getSignInRecordById")
    void testGetSignInRecordById() {
        when(signInRecordRepository.findById(1L))
                .thenReturn(Mono.just(mockSignInRecord));

        Mono<SignInRecordVO> result = checkService.getSignInRecordById(1L);

        StepVerifier.create(result)
                .expectNextMatches(vo -> vo.getId() == 1L)
                .verifyComplete();
    }

    @Test
    @DisplayName("测试5: 查询签到记录 - 记录不存在")
    void testGetSignInRecordById_NotFound() {
        when(signInRecordRepository.findById(999L))
                .thenReturn(Mono.empty());

        Mono<SignInRecordVO> result = checkService.getSignInRecordById(999L);

        StepVerifier.create(result)
                .verifyComplete();
    }

    @Test
    @DisplayName("测试6: 获取签到统计 - getSignInStats")
    void testGetSignInStats() {
        when(signInRecordRepository.countByMemberIdAndTimeRange(
                eq(1L), any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Mono.just(10L));
        when(signInRecordRepository.countSuccessByMemberIdAndTimeRange(
                eq(1L), any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Mono.just(8L));
        when(signInRecordRepository.getFirstSignInTime(
                eq(1L), any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Mono.just(LocalDateTime.now().minusDays(29)));
        when(signInRecordRepository.getLastSignInTime(
                eq(1L), any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Mono.just(LocalDateTime.now()));

        Mono<SignInStatsVO> result = checkService.getSignInStats(1L, 
                LocalDate.now().minusDays(30), LocalDate.now());

        StepVerifier.create(result)
                .expectNextMatches(stats -> {
                    org.junit.jupiter.api.Assertions.assertEquals(10L, stats.getTotalCount());
                    org.junit.jupiter.api.Assertions.assertEquals(8L, stats.getSuccessCount());
                    return true;
                })
                .verifyComplete();
    }

    @Test
    @DisplayName("测试7: 获取每日签到统计 - getDailySignInStats")
    void testGetDailySignInStats() {
        when(signInRecordRepository.countByTimeRange(any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Mono.just(50L));
        when(signInRecordRepository.countSuccessByTimeRange(
                any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Mono.just(45L));
        when(signInRecordRepository.countDistinctMembersByTimeRange(
                any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Mono.just(30L));

        Mono<SignInStatsVO> result = checkService.getDailySignInStats(LocalDate.now());

        StepVerifier.create(result)
                .expectNextMatches(stats -> {
                    org.junit.jupiter.api.Assertions.assertEquals(50L, stats.getTotalCount());
                    org.junit.jupiter.api.Assertions.assertEquals(45L, stats.getSuccessCount());
                    org.junit.jupiter.api.Assertions.assertEquals(30L, stats.getUniqueMemberCount());
                    return true;
                })
                .verifyComplete();
    }

    @Test
    @DisplayName("测试8: 导出签到记录 - exportSignInRecords")
    void testExportSignInRecords() {
        when(signInRecordRepository.findByMemberIdAndTimeRange(
                eq(1L), any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Flux.just(mockSignInRecord));

        Mono<byte[]> result = checkService.exportSignInRecords(1L, 
                LocalDate.now().minusDays(7), LocalDate.now());

        StepVerifier.create(result)
                .expectNextMatches(bytes -> bytes.length > 0)
                .verifyComplete();
    }

    @Test
    @DisplayName("测试9: 签到失败 - 二维码无效")
    void testCheckIn_QRCodeInvalid() {
        Long memberId = 1L;
        String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();
        when(redisUtil.get(eq(key))).thenReturn(Mono.just(new HashMap<>()));
        when(signInRecordRepository.findByMemberIdAndDate(eq(memberId), any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Mono.empty());

        Mono<String> result = checkService.checkIn(memberId, "invalid-qr");

        StepVerifier.create(result)
                .expectError(RuntimeException.class)
                .verify();
    }

    @Test
    @DisplayName("测试10: 签到失败 - 二维码不存在")
    void testCheckIn_QRCodeNotFound() {
        Long memberId = 1L;
        String key = RedisKeyConstants.QRCODE_USER_DAILY + memberId + LocalDate.now();
        when(redisUtil.get(eq(key))).thenReturn(Mono.empty());
        when(signInRecordRepository.findByMemberIdAndDate(eq(memberId), any(LocalDateTime.class), any(LocalDateTime.class)))
                .thenReturn(Mono.empty());

        Mono<String> result = checkService.checkIn(memberId, "not-exist");

        StepVerifier.create(result)
                .verifyComplete();
    }
}
