package com.mosquito.project.service;

import com.mosquito.project.dto.ShareMetricsResponse;
import com.mosquito.project.dto.ShareTrackingResponse;
import com.mosquito.project.persistence.entity.ActivityEntity;
import com.mosquito.project.persistence.entity.LinkClickEntity;
import com.mosquito.project.persistence.repository.ActivityRepository;
import com.mosquito.project.persistence.repository.LinkClickRepository;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.*;

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

@ExtendWith(MockitoExtension.class)
@DisplayName("分享跟踪服务测试")
class ShareTrackingServiceTest {

    @Mock
    private LinkClickRepository linkClickRepository;

    @Mock
    private ActivityRepository activityRepository;

    @Mock
    private ShareConfigService shareConfigService;

    @InjectMocks
    private ShareTrackingService shareTrackingService;

    private final Long ACTIVITY_ID = 123L;
    private final Long INVITER_ID = 456L;
    private final String SOURCE = "wechat";
    private final String SHORT_CODE = "abc12345";
    private final String IP = "192.168.1.1";
    private final String USER_AGENT = "Mozilla/5.0 (Test Browser)";
    private final String REFERER = "https://google.com";

    @BeforeEach
    void setUp() {
        lenient().when(linkClickRepository.save(any(LinkClickEntity.class))).thenAnswer(invocation -> {
            LinkClickEntity entity = invocation.getArgument(0);
            entity.setId(1L);
            entity.setCreatedAt(OffsetDateTime.now(ZoneOffset.UTC));
            return entity;
        });
    }

    @Test
    @DisplayName("创建分享跟踪 - 基本功能")
    void shouldCreateShareTracking_Basic() {
        // When
        ShareTrackingResponse result = shareTrackingService.createShareTracking(ACTIVITY_ID, INVITER_ID, SOURCE, null);

        // Then
        assertNotNull(result);
        assertNotNull(result.getTrackingId());
        assertNotNull(result.getShortCode());
        assertEquals(ACTIVITY_ID, result.getActivityId());
        assertEquals(INVITER_ID, result.getInviterUserId());
        verifyNoInteractions(linkClickRepository, activityRepository);
    }

    @Test
    @DisplayName("创建分享跟踪 - 带参数")
    void shouldCreateShareTracking_WithParams() {
        Map<String, String> params = Map.of("param1", "value1", "param2", "value2");

        // When
        ShareTrackingResponse result = shareTrackingService.createShareTracking(ACTIVITY_ID, INVITER_ID, SOURCE, params);

        // Then
        assertNotNull(result);
        assertNotNull(result.getTrackingId());
        assertNotNull(result.getShortCode());
        assertEquals(ACTIVITY_ID, result.getActivityId());
        assertEquals(INVITER_ID, result.getInviterUserId());
    }

    @Test
    @DisplayName("记录点击 - 基本功能")
    void shouldRecordClick_Basic() {
        // When
        shareTrackingService.recordClick(SHORT_CODE, IP, USER_AGENT, REFERER, null);

        // Then
        verify(linkClickRepository).save(argThat(entity -> {
            assertEquals(SHORT_CODE, entity.getCode());
            assertEquals(IP, entity.getIp());
            assertEquals(USER_AGENT, entity.getUserAgent());
            assertEquals(REFERER, entity.getReferer());
            assertNotNull(entity.getCreatedAt());
            return true;
        }));
    }

    @Test
    @DisplayName("记录点击 - 带参数")
    void shouldRecordClick_WithParams() {
        Map<String, String> params = Map.of("source", "wechat", "campaign", "summer");

        // When
        shareTrackingService.recordClick(SHORT_CODE, IP, USER_AGENT, REFERER, params);

        // Then
        verify(linkClickRepository).save(argThat(entity -> {
            assertEquals(params, entity.getParams());
            return true;
        }));
    }

    @Test
    @DisplayName("记录点击 - 异常处理")
    void shouldHandleException_WhenRecordClick() {
        // Given
        doThrow(new RuntimeException("Database error")).when(linkClickRepository).save(any(LinkClickEntity.class));

        // When & Then - 应该不抛出异常
        assertDoesNotThrow(() -> {
            shareTrackingService.recordClick(SHORT_CODE, IP, USER_AGENT, REFERER, null);
        });
        verify(linkClickRepository).save(any(LinkClickEntity.class));
    }

    @Test
    @DisplayName("获取分享指标 - 无点击数据")
    void shouldReturnEmptyMetrics_WhenNoClicks() {
        // Given
        OffsetDateTime startTime = OffsetDateTime.now(ZoneOffset.UTC).minusDays(1);
        OffsetDateTime endTime = OffsetDateTime.now(ZoneOffset.UTC);
        when(linkClickRepository.findByActivityIdAndCreatedAtBetween(ACTIVITY_ID, startTime, endTime))
                .thenReturn(Collections.emptyList());

        // When
        ShareMetricsResponse result = shareTrackingService.getShareMetrics(ACTIVITY_ID, startTime, endTime);

        // Then
        assertEquals(ACTIVITY_ID, result.getActivityId());
        assertEquals(startTime, result.getStartTime());
        assertEquals(endTime, result.getEndTime());
        assertEquals(0L, result.getTotalClicks());
        assertTrue(result.getSourceDistribution().isEmpty());
        assertTrue(result.getHourlyDistribution().isEmpty());
        assertEquals(0L, result.getUniqueVisitors());
    }

    @Test
    @DisplayName("获取分享指标 - 有点击数据")
    void shouldCalculateMetrics_WhenClicksExist() {
        // Given
        OffsetDateTime startTime = OffsetDateTime.now(ZoneOffset.UTC).minusDays(1);
        OffsetDateTime endTime = OffsetDateTime.now(ZoneOffset.UTC);
        
        List<LinkClickEntity> clicks = createTestClicks(startTime);
        when(linkClickRepository.findByActivityIdAndCreatedAtBetween(ACTIVITY_ID, startTime, endTime))
                .thenReturn(clicks);

        // When
        ShareMetricsResponse result = shareTrackingService.getShareMetrics(ACTIVITY_ID, startTime, endTime);

        // Then
        assertEquals(3L, result.getTotalClicks());
        assertEquals(2L, result.getUniqueVisitors()); // 2 unique IPs
        
        Map<String, Long> expectedSources = Map.of(
            "wechat", 2L,
            "unknown", 1L
        );
        assertEquals(expectedSources, result.getSourceDistribution());
        
        assertFalse(result.getHourlyDistribution().isEmpty());
    }

    @Test
    @DisplayName("获取顶级分享链接")
    void shouldGetTopShareLinks() {
        // Given
        List<Object[]> mockResults = Arrays.asList(
            new Object[]{"code1", 10L, 1L},
            new Object[]{"code2", 5L, 2L},
            new Object[]{"code3", 3L, 3L}
        );
        when(linkClickRepository.findTopSharedLinksByActivityId(ACTIVITY_ID, 5))
                .thenReturn(mockResults);

        // When
        List<Map<String, Object>> result = shareTrackingService.getTopShareLinks(ACTIVITY_ID, 5);

        // Then
        assertEquals(3, result.size());
        
        Map<String, Object> first = result.get(0);
        assertEquals("code1", first.get("shortCode"));
        assertEquals(10L, first.get("clickCount"));
        assertEquals(1L, first.get("inviterUserId"));
        
        Map<String, Object> second = result.get(1);
        assertEquals("code2", second.get("shortCode"));
        assertEquals(5L, second.get("clickCount"));
        assertEquals(2L, second.get("inviterUserId"));
    }

    @Test
    @DisplayName("获取转化漏斗")
    void shouldGetConversionFunnel() {
        // Given
        OffsetDateTime startTime = OffsetDateTime.now(ZoneOffset.UTC).minusDays(1);
        OffsetDateTime endTime = OffsetDateTime.now(ZoneOffset.UTC);
        
        List<LinkClickEntity> clicks = createTestClicksWithReferers();
        when(linkClickRepository.findByActivityIdAndCreatedAtBetween(ACTIVITY_ID, startTime, endTime))
                .thenReturn(clicks);

        // When
        Map<String, Object> result = shareTrackingService.getConversionFunnel(ACTIVITY_ID, startTime, endTime);

        // Then
        assertEquals(3L, result.get("totalClicks"));
        assertEquals(2L, result.get("withReferer"));
        assertEquals(3L, result.get("withUserAgent"));
        assertEquals(2.0/3.0, (Double) result.get("refererRate"), 0.01);
        
        @SuppressWarnings("unchecked")
        Map<String, Long> topReferers = (Map<String, Long>) result.get("topReferers");
        assertEquals(1L, topReferers.get("google.com"));
        assertEquals(1L, topReferers.get("facebook.com"));
    }

    @Test
    @DisplayName("获取转化漏斗 - 无数据")
    void shouldGetEmptyConversionFunnel_WhenNoData() {
        // Given
        OffsetDateTime startTime = OffsetDateTime.now(ZoneOffset.UTC).minusDays(1);
        OffsetDateTime endTime = OffsetDateTime.now(ZoneOffset.UTC);
        when(linkClickRepository.findByActivityIdAndCreatedAtBetween(ACTIVITY_ID, startTime, endTime))
                .thenReturn(Collections.emptyList());

        // When
        Map<String, Object> result = shareTrackingService.getConversionFunnel(ACTIVITY_ID, startTime, endTime);

        // Then
        assertEquals(0L, result.get("totalClicks"));
        assertEquals(0L, result.get("withReferer"));
        assertEquals(0L, result.get("withUserAgent"));
        assertEquals(0.0, (Double) result.get("refererRate"), 0.01);
        assertTrue(((Map<?, ?>) result.get("topReferers")).isEmpty());
    }

    @Test
    @DisplayName("域名提取 - 处理无效URL")
    void shouldHandleInvalidUrl_WhenExtractingDomain() {
        // Given
        List<LinkClickEntity> clicks = List.of(
            createClickWithReferer("invalid-url"),
            createClickWithReferer("not-even-a-url")
        );
        when(linkClickRepository.findByActivityIdAndCreatedAtBetween(eq(ACTIVITY_ID), any(), any()))
                .thenReturn(clicks);

        // When
        Map<String, Object> result = shareTrackingService.getConversionFunnel(ACTIVITY_ID, 
                OffsetDateTime.now().minusDays(1), OffsetDateTime.now());

        // Then
        @SuppressWarnings("unchecked")
        Map<String, Long> topReferers = (Map<String, Long>) result.get("topReferers");
        assertEquals(2L, topReferers.get("unknown"));
    }

    private List<LinkClickEntity> createTestClicks(OffsetDateTime baseTime) {
        List<LinkClickEntity> clicks = new ArrayList<>();
        
        Map<String, String> params1 = Map.of("source", "wechat");
        Map<String, String> params2 = Map.of("source", "wechat");
        Map<String, String> params3 = new HashMap<>(); // no params = unknown source
        
        LinkClickEntity click1 = createClick("192.168.1.1", baseTime, params1);
        LinkClickEntity click2 = createClick("192.168.1.1", baseTime.plusHours(1), params2);
        LinkClickEntity click3 = createClick("192.168.1.2", baseTime.plusHours(2), params3);
        
        clicks.addAll(Arrays.asList(click1, click2, click3));
        return clicks;
    }

    private List<LinkClickEntity> createTestClicksWithReferers() {
        List<LinkClickEntity> clicks = new ArrayList<>();
        
        LinkClickEntity click1 = createClickWithReferer("https://google.com/search?q=test");
        LinkClickEntity click2 = createClickWithReferer("https://facebook.com/posts/123");
        LinkClickEntity click3 = createClickWithReferer(null); // no referer
        
        clicks.addAll(Arrays.asList(click1, click2, click3));
        return clicks;
    }

    private LinkClickEntity createClick(String ip, OffsetDateTime time, Map<String, String> params) {
        LinkClickEntity click = new LinkClickEntity();
        click.setCode(SHORT_CODE);
        click.setIp(ip);
        click.setUserAgent(USER_AGENT);
        click.setCreatedAt(time);
        click.setParams(params);
        return click;
    }

    private LinkClickEntity createClickWithReferer(String referer) {
        LinkClickEntity click = new LinkClickEntity();
        click.setCode(SHORT_CODE);
        click.setIp(IP);
        click.setUserAgent(USER_AGENT);
        click.setReferer(referer);
        click.setCreatedAt(OffsetDateTime.now(ZoneOffset.UTC));
        return click;
    }
}
