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

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

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.starry.admin.common.domain.LoginUser;
import com.starry.admin.common.exception.ServiceException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailQueryVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailResponseVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewQueryVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewResponseVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewSummaryVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceSnapshotVo;
import com.starry.admin.modules.statistics.service.impl.PlayClerkPerformanceServiceImpl;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.Month;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;

/**
 * Unit tests for {@link PlayClerkPerformanceServiceImpl} covering aggregation, ranking and detail logic.
 */
@ExtendWith(MockitoExtension.class)
class PlayClerkPerformanceServiceImplTest {

    @Mock
    private IPlayClerkUserInfoService clerkUserInfoService;

    @Mock
    private IPlayOrderInfoService playOrderInfoService;

    @Mock
    private IPlayClerkLevelInfoService playClerkLevelInfoService;

    @Mock
    private IPlayPersonnelGroupInfoService playPersonnelGroupInfoService;

    @InjectMocks
    private PlayClerkPerformanceServiceImpl service;

    @Test
    @DisplayName("queryOverview should aggregate metrics and sort clerks by GMV")
    void queryOverviewAggregatesAndSorts() {
        ClerkPerformanceOverviewQueryVo vo = new ClerkPerformanceOverviewQueryVo();
        vo.setEndOrderTime(Arrays.asList("2024-08-01 00:00:00", "2024-08-07 23:59:59"));
        vo.setIncludeSummary(true);
        vo.setIncludeRankings(true);

        PlayClerkUserInfoEntity clerkA = buildClerk("c1", "Alice", "g1", "l1");
        PlayClerkUserInfoEntity clerkB = buildClerk("c2", "Bob", "g2", "l2");

        when(playPersonnelGroupInfoService.getValidClerkIdList(any(), any())).thenReturn(Arrays.asList("c1", "c2"));
        when(clerkUserInfoService.list((Wrapper<PlayClerkUserInfoEntity>) any())).thenReturn(Arrays.asList(clerkA, clerkB));
        when(playClerkLevelInfoService.selectAll()).thenReturn(Arrays.asList(level("l1", "白银"), level("l2", "黄金")));
        when(playPersonnelGroupInfoService.selectAll())
                .thenReturn(Arrays.asList(group("g1", "一组"), group("g2", "二组")));

        List<PlayOrderInfoEntity> ordersA = Arrays.asList(
                order("c1", "userA", "1", "0", "0", new BigDecimal("100.00"), new BigDecimal("60.00"),
                        LocalDateTime.of(2024, Month.AUGUST, 1, 10, 0)),
                order("c1", "userA", "0", "2", "0", new BigDecimal("150.00"), new BigDecimal("90.00"),
                        LocalDateTime.of(2024, Month.AUGUST, 2, 14, 0)),
                withRefund(order("c1", "userB", "0", "0", "1", new BigDecimal("50.00"), new BigDecimal("30.00"),
                        LocalDateTime.of(2024, Month.AUGUST, 3, 9, 0)), new BigDecimal("30.00")));

        List<PlayOrderInfoEntity> ordersB = Collections.singletonList(
                order("c2", "userC", "1", "0", "0", new BigDecimal("80.00"), new BigDecimal("50.00"),
                        LocalDateTime.of(2024, Month.AUGUST, 1, 12, 0)));

        when(playOrderInfoService.clerkSelectOrderInfoList(eq("c1"), anyString(), anyString())).thenReturn(ordersA);
        when(playOrderInfoService.clerkSelectOrderInfoList(eq("c2"), anyString(), anyString())).thenReturn(ordersB);

        setAuthentication();
        try {
            ClerkPerformanceOverviewResponseVo response = service.queryOverview(vo);

            assertNotNull(response.getSummary(), "summary should be present when includeSummary=true");
            assertEquals(2, response.getTotal());
            assertEquals(2, response.getRankings().size());

            ClerkPerformanceSnapshotVo top = response.getRankings().get(0);
            assertEquals("c1", top.getClerkId(), "Highest GMV clerk should rank first");
            assertEquals(new BigDecimal("300.00"), top.getGmv());
            assertEquals(new BigDecimal("100.00"), top.getFirstOrderAmount());
            assertEquals(new BigDecimal("200.00"), top.getContinuedOrderAmount());
            assertEquals(new BigDecimal("150.00"), top.getRewardAmount());
            assertEquals(new BigDecimal("30.00"), top.getRefundAmount());
            assertEquals(3, top.getOrderCount());
            assertEquals(2, top.getContinuedOrderCount());
            assertEquals(new BigDecimal("66.67"), top.getContinuedRate());
            assertEquals(new BigDecimal("100.00"), top.getAverageTicketPrice());

            ClerkPerformanceOverviewSummaryVo summary = response.getSummary();
            assertEquals(new BigDecimal("380.00"), summary.getTotalGmv());
            assertEquals(4, summary.getTotalOrderCount());
            assertEquals(2, summary.getTotalFirstOrderCount());
            assertEquals(2, summary.getTotalContinuedOrderCount());
            assertEquals(new BigDecimal("50.00"), summary.getContinuedRate());
            assertEquals(new BigDecimal("95.00"), summary.getAverageTicketPrice());
        } finally {
            clearAuthentication();
        }
    }

    @Test
    @DisplayName("queryDetail should build snapshot, composition and trend for accessible clerk")
    void queryDetailBuildsSnapshotCompositionAndTrend() {
        ClerkPerformanceDetailQueryVo vo = new ClerkPerformanceDetailQueryVo();
        vo.setClerkId("c1");
        vo.setEndOrderTime(Arrays.asList("2024-08-01 00:00:00", "2024-08-03 23:59:59"));
        vo.setIncludeTrend(true);
        vo.setTrendDays(3);

        PlayClerkUserInfoEntity clerk = buildClerk("c1", "Alice", "g1", "l1");

        when(playPersonnelGroupInfoService.getValidClerkIdList(any(), any())).thenReturn(Collections.singletonList("c1"));
        when(clerkUserInfoService.getById("c1")).thenReturn(clerk);
        when(playClerkLevelInfoService.selectAll()).thenReturn(Collections.singletonList(level("l1", "白银")));
        when(playPersonnelGroupInfoService.selectAll()).thenReturn(Collections.singletonList(group("g1", "一组")));

        List<PlayOrderInfoEntity> orders = Arrays.asList(
                order("c1", "userA", "1", "0", "0", new BigDecimal("120.00"), new BigDecimal("70.00"),
                        LocalDateTime.of(2024, Month.AUGUST, 1, 9, 0)),
                order("c1", "userA", "0", "0", "0", new BigDecimal("80.00"), new BigDecimal("40.00"),
                        LocalDateTime.of(2024, Month.AUGUST, 2, 10, 0)),
                withRefund(order("c1", "userB", "0", "2", "1", new BigDecimal("60.00"), new BigDecimal("30.00"),
                        LocalDateTime.of(2024, Month.AUGUST, 2, 18, 0)), new BigDecimal("20.00")));
        when(playOrderInfoService.clerkSelectOrderInfoList(eq("c1"), anyString(), anyString())).thenReturn(orders);

        setAuthentication();
        try {
            ClerkPerformanceDetailResponseVo response = service.queryDetail(vo);
            assertNotNull(response.getProfile());
            assertEquals("Alice", response.getProfile().getNickname());
            assertEquals("一组", response.getProfile().getGroupName());
            assertNotNull(response.getSnapshot());
            assertEquals(new BigDecimal("260.00"), response.getSnapshot().getGmv());
            assertEquals(3, response.getSnapshot().getOrderCount());
            assertEquals(new BigDecimal("66.67"), response.getSnapshot().getContinuedRate());
            assertEquals(new BigDecimal("86.67"), response.getSnapshot().getAverageTicketPrice());

            assertNotNull(response.getComposition());
            assertEquals(4, response.getComposition().getOrderComposition().size());
            assertEquals(new BigDecimal("33.33"), response.getComposition().getOrderComposition().get(0).getCountRatio());

            assertEquals(3, response.getTrend().size());
            assertEquals("DAY", response.getTrendDimension());
            assertEquals(new BigDecimal("120.00"), response.getTrend().get(0).getGmv());
            assertEquals(new BigDecimal("140.00"), response.getTrend().get(1).getGmv());
        } finally {
            clearAuthentication();
        }
    }

    @Test
    @DisplayName("queryDetail should reject access when clerk not under current user")
    void queryDetailShouldEnforcePermissions() {
        ClerkPerformanceDetailQueryVo vo = new ClerkPerformanceDetailQueryVo();
        vo.setClerkId("c99");
        vo.setEndOrderTime(Arrays.asList("2024-08-01 00:00:00", "2024-08-02 23:59:59"));

        when(playPersonnelGroupInfoService.getValidClerkIdList(any(), any())).thenReturn(Collections.singletonList("c1"));
        when(clerkUserInfoService.getById("c99")).thenReturn(buildClerk("c99", "Ghost", "g1", "l1"));

        setAuthentication();
        try {
            ServiceException ex = assertThrows(ServiceException.class, () -> service.queryDetail(vo));
            assertTrue(ex.getMessage().contains("无权"));
        } finally {
            clearAuthentication();
        }
    }

    private PlayClerkUserInfoEntity buildClerk(String id, String name, String groupId, String levelId) {
        PlayClerkUserInfoEntity entity = new PlayClerkUserInfoEntity();
        entity.setId(id);
        entity.setNickname(name);
        entity.setGroupId(groupId);
        entity.setLevelId(levelId);
        entity.setSex("1");
        entity.setListingState("1");
        entity.setOnlineState("1");
        return entity;
    }

    private PlayClerkLevelInfoEntity level(String id, String name) {
        PlayClerkLevelInfoEntity level = new PlayClerkLevelInfoEntity();
        level.setId(id);
        level.setName(name);
        return level;
    }

    private PlayPersonnelGroupInfoEntity group(String id, String name) {
        PlayPersonnelGroupInfoEntity entity = new PlayPersonnelGroupInfoEntity();
        entity.setId(id);
        entity.setGroupName(name);
        return entity;
    }

    private PlayOrderInfoEntity order(String clerkId, String purchaser, String firstOrder, String placeType,
            String refundType, BigDecimal finalAmount, BigDecimal estimatedRevenue, LocalDateTime purchaserTime) {
        PlayOrderInfoEntity order = new PlayOrderInfoEntity();
        order.setAcceptBy(clerkId);
        order.setPurchaserBy(purchaser);
        order.setFirstOrder(firstOrder);
        order.setPlaceType(placeType);
        order.setRefundType(refundType);
        order.setFinalAmount(finalAmount);
        order.setEstimatedRevenue(estimatedRevenue);
        order.setOrdersExpiredState("1".equals(refundType) ? "1" : "0");
        order.setPurchaserTime(purchaserTime);
        return order;
    }

    private void setAuthentication() {
        LoginUser loginUser = new LoginUser();
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    private void clearAuthentication() {
        SecurityContextHolder.clearContext();
    }

    private PlayOrderInfoEntity withRefund(PlayOrderInfoEntity order, BigDecimal refundAmount) {
        order.setRefundAmount(refundAmount);
        return order;
    }
}
