package com.mosquito.project.persistence.repository;

import com.mosquito.project.persistence.entity.LinkClickEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.TestPropertySource;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

/**
 * LinkClickRepository 数据访问层测试
 * 测试链接点击记录的CRUD操作和分析查询方法
 */
@DataJpaTest
@org.springframework.context.annotation.Import(com.mosquito.project.config.TestCacheConfig.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
@TestPropertySource(properties = {
        "spring.cache.type=NONE",
        "spring.jpa.hibernate.ddl-auto=create-drop",
        "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration"
})
class LinkClickRepositoryTest {

    @Autowired
    private LinkClickRepository linkClickRepository;

    private static final Long ACTIVITY_ID_1 = 1L;
    private static final Long ACTIVITY_ID_2 = 2L;
    private static final Long USER_1 = 101L;
    private static final Long USER_2 = 102L;
    private static final String CODE_1 = "abc123";
    private static final String CODE_2 = "def456";
    private OffsetDateTime now;

    @BeforeEach
    void setUp() {
        now = OffsetDateTime.now(ZoneOffset.UTC);
        linkClickRepository.deleteAll();
    }

    @Test
    void shouldSaveAndFindLinkClickById() {
        // 创建点击记录
        LinkClickEntity click = createClick(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.1");
        LinkClickEntity saved = linkClickRepository.save(click);

        // 验证保存
        assertNotNull(saved.getId(), "保存后ID不应为空");
        assertEquals(CODE_1, saved.getCode());

        // 通过ID查询
        LinkClickEntity found = linkClickRepository.findById(saved.getId()).orElseThrow();
        assertEquals("192.168.1.1", found.getIp());
    }

    @Test
    void shouldFindByActivityId() {
        // 为活动1创建点击记录
        linkClickRepository.save(createClick(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.1"));
        linkClickRepository.save(createClick(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.2"));
        linkClickRepository.save(createClick(CODE_2, ACTIVITY_ID_1, USER_2, "192.168.1.3"));

        // 为活动2创建点击记录（应该被排除）
        linkClickRepository.save(createClick(CODE_1, ACTIVITY_ID_2, USER_1, "192.168.2.1"));

        // 查询活动1的所有点击
        List<LinkClickEntity> clicks = linkClickRepository.findByActivityId(ACTIVITY_ID_1);

        // 验证
        assertEquals(3, clicks.size(), "活动1应该有3条点击记录");
        assertTrue(clicks.stream().allMatch(c -> c.getActivityId().equals(ACTIVITY_ID_1)),
                "所有记录都应属于活动1");
    }

    @Test
    void shouldFindByActivityIdAndCreatedAtBetween() {
        // 创建不同时间的点击记录
        OffsetDateTime twoHoursAgo = now.minusHours(2);
        OffsetDateTime oneHourAgo = now.minusHours(1);
        OffsetDateTime thirtyMinutesAgo = now.minusMinutes(30);

        linkClickRepository.save(createClickWithTime(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.1", twoHoursAgo));
        linkClickRepository.save(createClickWithTime(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.2", oneHourAgo));
        linkClickRepository.save(createClickWithTime(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.3", thirtyMinutesAgo));

        // 查询过去1.5小时内的点击
        List<LinkClickEntity> clicks = linkClickRepository.findByActivityIdAndCreatedAtBetween(
                ACTIVITY_ID_1, now.minusMinutes(90), now);

        // 验证 - 应该返回过去1.5小时内的2条记录
        assertEquals(2, clicks.size(), "过去1.5小时内应该有2条点击记录");
    }

    @Test
    void shouldFindByCode() {
        // 创建不同code的点击记录
        linkClickRepository.save(createClick(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.1"));
        linkClickRepository.save(createClick(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.2"));
        linkClickRepository.save(createClick(CODE_2, ACTIVITY_ID_1, USER_2, "192.168.1.3"));

        // 查询CODE_1的点击
        List<LinkClickEntity> clicks = linkClickRepository.findByCode(CODE_1);

        // 验证
        assertEquals(2, clicks.size(), "CODE_1应该有2条点击记录");
        assertTrue(clicks.stream().allMatch(c -> c.getCode().equals(CODE_1)),
                "所有记录都应该是CODE_1");
    }

    @Test
    void shouldCountByActivityId() {
        // 创建测试数据
        linkClickRepository.save(createClick(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.1"));
        linkClickRepository.save(createClick(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.2"));
        linkClickRepository.save(createClick(CODE_2, ACTIVITY_ID_1, USER_2, "192.168.1.3"));
        linkClickRepository.save(createClick(CODE_1, ACTIVITY_ID_2, USER_1, "192.168.2.1"));

        // 统计活动1的点击数
        long count = linkClickRepository.countByActivityId(ACTIVITY_ID_1);

        // 验证
        assertEquals(3, count, "活动1应该有3条点击记录");
    }

    @Test
    void shouldCountUniqueVisitorsByActivityIdAndDateRange() {
        // 创建来自不同IP的点击记录（有些是重复IP）
        OffsetDateTime startTime = now.minusHours(1);
        OffsetDateTime endTime = now;

        // 同一IP多次点击
        linkClickRepository.save(createClickWithTime(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.1", startTime.plusMinutes(10)));
        linkClickRepository.save(createClickWithTime(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.1", startTime.plusMinutes(20)));
        linkClickRepository.save(createClickWithTime(CODE_2, ACTIVITY_ID_1, USER_2, "192.168.1.2", startTime.plusMinutes(15)));
        linkClickRepository.save(createClickWithTime(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.3", startTime.plusMinutes(30)));

        // 范围外的点击（应该被排除）
        linkClickRepository.save(createClickWithTime(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.4", now.minusHours(2)));
        linkClickRepository.save(createClickWithTime(CODE_1, ACTIVITY_ID_2, USER_1, "192.168.1.5", startTime.plusMinutes(5)));

        // 查询独立访客数
        long uniqueVisitors = linkClickRepository.countUniqueVisitorsByActivityIdAndDateRange(
                ACTIVITY_ID_1, startTime, endTime);

        // 验证 - 应该有3个独立IP
        assertEquals(3, uniqueVisitors, "应该有3个独立访客（192.168.1.1, 192.168.1.2, 192.168.1.3）");
    }

    @Test
    void shouldFindTopSharedLinksByActivityId() {
        // 创建点击数据：CODE_1有5次点击，CODE_2有3次点击，CODE_3有1次点击
        for (int i = 0; i < 5; i++) {
            linkClickRepository.save(createClick(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1." + i));
        }
        for (int i = 0; i < 3; i++) {
            linkClickRepository.save(createClick(CODE_2, ACTIVITY_ID_1, USER_2, "192.168.2." + i));
        }
        linkClickRepository.save(createClick("xyz789", ACTIVITY_ID_1, USER_1, "192.168.3.1"));

        // 为活动2创建点击（应该被排除）
        for (int i = 0; i < 10; i++) {
            linkClickRepository.save(createClick(CODE_1, ACTIVITY_ID_2, USER_1, "192.168.4." + i));
        }

        // 查询活动1的热门链接（前2个）
        List<Object[]> topLinks = linkClickRepository.findTopSharedLinksByActivityId(ACTIVITY_ID_1, 2);

        // 验证
        assertEquals(2, topLinks.size(), "应该返回前2个热门链接");

        // 验证排序
        assertEquals(CODE_1, topLinks.get(0)[0], "第一名应该是CODE_1");
        assertEquals(5L, topLinks.get(0)[1], "CODE_1应该有5次点击");

        assertEquals(CODE_2, topLinks.get(1)[0], "第二名应该是CODE_2");
        assertEquals(3L, topLinks.get(1)[1], "CODE_2应该有3次点击");
    }

    @Test
    void shouldStoreAndRetrieveParams() {
        // 创建带参数的点击记录
        LinkClickEntity click = createClick(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.1");
        Map<String, String> params = new HashMap<>();
        params.put("utm_source", "wechat");
        params.put("utm_medium", "share");
        params.put("campaign", "summer2024");
        click.setParams(params);

        LinkClickEntity saved = linkClickRepository.save(click);

        // 查询并验证参数
        LinkClickEntity found = linkClickRepository.findById(saved.getId()).orElseThrow();
        Map<String, String> retrievedParams = found.getParams();

        assertNotNull(retrievedParams);
        assertEquals("wechat", retrievedParams.get("utm_source"));
        assertEquals("share", retrievedParams.get("utm_medium"));
        assertEquals("summer2024", retrievedParams.get("campaign"));
    }

    @Test
    void shouldReturnEmptyListForNonExistentActivity() {
        // 查询不存在的活动
        List<LinkClickEntity> clicks = linkClickRepository.findByActivityId(999L);
        assertTrue(clicks.isEmpty(), "不存在的活动应该返回空列表");

        // 统计不存在的活动
        long count = linkClickRepository.countByActivityId(999L);
        assertEquals(0, count, "不存在的活动计数应该为0");
    }

    @Test
    void shouldHandleLargeNumberOfClicks() {
        // 批量创建1000条点击记录
        for (int i = 0; i < 1000; i++) {
            String ip = "192.168." + (i / 256) + "." + (i % 256);
            linkClickRepository.save(createClick(CODE_1, ACTIVITY_ID_1, USER_1, ip));
        }

        // 验证计数
        long count = linkClickRepository.countByActivityId(ACTIVITY_ID_1);
        assertEquals(1000, count, "应该有1000条点击记录");

        // 验证独立访客数
        long uniqueVisitors = linkClickRepository.countUniqueVisitorsByActivityIdAndDateRange(
                ACTIVITY_ID_1, now.minusHours(1), now.plusHours(1));
        assertEquals(1000, uniqueVisitors, "应该有1000个独立访客");
    }

    @Test
    void shouldStoreAllMetadataFields() {
        // 创建完整的点击记录
        LinkClickEntity click = new LinkClickEntity();
        click.setCode(CODE_1);
        click.setActivityId(ACTIVITY_ID_1);
        click.setInviterUserId(USER_1);
        click.setIp("192.168.1.1");
        click.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
        click.setReferer("https://example.com/page");
        click.setCreatedAt(now);

        Map<String, String> params = new HashMap<>();
        params.put("track_id", "12345");
        click.setParams(params);

        LinkClickEntity saved = linkClickRepository.save(click);

        // 查询并验证所有字段
        LinkClickEntity found = linkClickRepository.findById(saved.getId()).orElseThrow();
        assertEquals(CODE_1, found.getCode());
        assertEquals(ACTIVITY_ID_1, found.getActivityId());
        assertEquals(USER_1, found.getInviterUserId());
        assertEquals("192.168.1.1", found.getIp());
        assertEquals("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", found.getUserAgent());
        assertEquals("https://example.com/page", found.getReferer());
        assertNotNull(found.getCreatedAt());
        assertNotNull(found.getParams());
    }

    @Test
    void shouldDeleteClickRecord() {
        // 创建并保存记录
        LinkClickEntity click = createClick(CODE_1, ACTIVITY_ID_1, USER_1, "192.168.1.1");
        LinkClickEntity saved = linkClickRepository.save(click);
        Long id = saved.getId();

        // 删除
        linkClickRepository.deleteById(id);

        // 验证删除
        assertFalse(linkClickRepository.existsById(id), "删除后应该不存在");
    }

    /**
     * 辅助方法：创建点击实体
     */
    private LinkClickEntity createClick(String code, Long activityId, Long inviterUserId, String ip) {
        LinkClickEntity click = new LinkClickEntity();
        click.setCode(code);
        click.setActivityId(activityId);
        click.setInviterUserId(inviterUserId);
        click.setIp(ip);
        click.setUserAgent("Mozilla/5.0 (Test)");
        click.setCreatedAt(now);
        return click;
    }

    /**
     * 辅助方法：创建带指定时间的点击实体
     */
    private LinkClickEntity createClickWithTime(String code, Long activityId, Long inviterUserId, String ip, OffsetDateTime time) {
        LinkClickEntity click = new LinkClickEntity();
        click.setCode(code);
        click.setActivityId(activityId);
        click.setInviterUserId(inviterUserId);
        click.setIp(ip);
        click.setUserAgent("Mozilla/5.0 (Test)");
        click.setCreatedAt(time);
        return click;
    }
}
