package com.mosquito.project.service;

import com.mosquito.project.config.TestCacheConfig;
import com.mosquito.project.domain.Activity;
import com.mosquito.project.dto.ActivityGraphResponse;
import com.mosquito.project.dto.CreateActivityRequest;
import com.mosquito.project.persistence.entity.UserInviteEntity;
import com.mosquito.project.persistence.repository.UserInviteRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.TestPropertySource;

import java.time.ZonedDateTime;
import java.util.List;

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

@SpringBootTest
@Import({TestCacheConfig.class})
@TestPropertySource(properties = {
    "spring.flyway.enabled=false",
    "spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration"
})
class ActivityAnalyticsServiceIntegrationTest {

    @Autowired
    private ActivityService activityService;

    @Autowired
    private UserInviteRepository userInviteRepository;

    @Test
    void leaderboardAndGraph_shouldReflectInvites() {
        CreateActivityRequest req = new CreateActivityRequest();
        req.setName("Graph Activity");
        req.setStartTime(ZonedDateTime.now().minusDays(1));
        req.setEndTime(ZonedDateTime.now().plusDays(1));
        Activity activity = activityService.createActivity(req);
        Long actId = activity.getId();

        // Invites: 1 -> 2, 1 -> 3, 2 -> 4
        saveInvite(actId, 1L, 2L);
        saveInvite(actId, 1L, 3L);
        saveInvite(actId, 2L, 4L);

        var leaderboard = activityService.getLeaderboard(actId);
        assertFalse(leaderboard.isEmpty());
        assertEquals(2, leaderboard.get(0).getScore()); // user 1 has two direct invites
        assertEquals(1L, leaderboard.get(0).getUserId());

        ActivityGraphResponse graph = activityService.getActivityGraph(actId);
        // Expect nodes: 1,2,3,4; edges: (1,2),(1,3),(2,4)
        assertEquals(4, graph.getNodes().size());
        assertEquals(3, graph.getEdges().size());
        assertTrue(graph.getEdges().stream().anyMatch(e -> e.getFrom().equals("1") && e.getTo().equals("2")));
        assertTrue(graph.getEdges().stream().anyMatch(e -> e.getFrom().equals("1") && e.getTo().equals("3")));
        assertTrue(graph.getEdges().stream().anyMatch(e -> e.getFrom().equals("2") && e.getTo().equals("4")));

        // With root=1, depth=1 -> only direct children of 1
        ActivityGraphResponse graphDepth1 = activityService.getActivityGraph(actId, 1L, 1, 10);
        assertTrue(graphDepth1.getEdges().stream().anyMatch(e -> e.getFrom().equals("1") && e.getTo().equals("2")));
        assertTrue(graphDepth1.getEdges().stream().anyMatch(e -> e.getFrom().equals("1") && e.getTo().equals("3")));
        assertEquals(2, graphDepth1.getEdges().size());
    }

    @Test
    void graphShouldHandleCyclesWithoutInfiniteLoop() {
        CreateActivityRequest req = new CreateActivityRequest();
        req.setName("Cycle Activity");
        req.setStartTime(ZonedDateTime.now().minusDays(1));
        req.setEndTime(ZonedDateTime.now().plusDays(1));
        Activity activity = activityService.createActivity(req);
        Long actId = activity.getId();

        // Cycle: 1->2, 2->3, 3->1
        saveInvite(actId, 1L, 2L);
        saveInvite(actId, 2L, 3L);
        saveInvite(actId, 3L, 1L);

        ActivityGraphResponse graph = activityService.getActivityGraph(actId, 1L, 10, 10);
        // Should include up to 3 edges and not loop infinitely
        assertTrue(graph.getEdges().size() <= 3);
        assertTrue(graph.getNodes().size() <= 3);
    }

    @Test
    void graphShouldEnforceEdgeLimitOnLargeGraph() {
        CreateActivityRequest req = new CreateActivityRequest();
        req.setName("Large Graph Activity");
        req.setStartTime(ZonedDateTime.now().minusDays(1));
        req.setEndTime(ZonedDateTime.now().plusDays(1));
        Activity activity = activityService.createActivity(req);
        Long actId = activity.getId();

        // Star: 1 -> 2..5000
        for (long i = 2; i <= 5000; i++) {
            saveInvite(actId, 1L, i);
        }

        int limit = 1000;
        ActivityGraphResponse graph = activityService.getActivityGraph(actId, 1L, 5, limit);
        assertEquals(limit, graph.getEdges().size());
        // Nodes should be limit + 1 at most (root + each edge's target), but may be less due to truncation order
        assertTrue(graph.getNodes().size() >= 2);
    }

    private void saveInvite(Long activityId, Long inviter, Long invitee) {
        UserInviteEntity e = new UserInviteEntity();
        e.setActivityId(activityId);
        e.setInviterUserId(inviter);
        e.setInviteeUserId(invitee);
        e.setCreatedAt(java.time.OffsetDateTime.now(java.time.ZoneOffset.UTC));
        e.setStatus("registered");
        userInviteRepository.save(e);
    }
}
