package org.raddatz.familienarchiv.dashboard;

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.raddatz.familienarchiv.audit.ActivityFeedRow;
import org.raddatz.familienarchiv.audit.AuditKind;
import org.raddatz.familienarchiv.audit.AuditLogQueryService;
import org.raddatz.familienarchiv.audit.PulseStatsRow;
import org.raddatz.familienarchiv.user.AppUser;
import org.raddatz.familienarchiv.document.Document;
import org.raddatz.familienarchiv.document.transcription.TranscriptionBlock;
import org.raddatz.familienarchiv.document.comment.CommentService;
import org.raddatz.familienarchiv.document.comment.CommentData;
import org.raddatz.familienarchiv.document.DocumentService;
import org.raddatz.familienarchiv.document.transcription.TranscriptionService;
import org.raddatz.familienarchiv.user.UserService;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class DashboardServiceTest {

    @Mock AuditLogQueryService auditLogQueryService;
    @Mock DocumentService documentService;
    @Mock TranscriptionService transcriptionService;
    @Mock UserService userService;
    @Mock CommentService commentService;

    @InjectMocks DashboardService dashboardService;

    // ─── getResume wires thumbnailUrl from Document ───────────────────────────

    @Test
    void getResume_populatesThumbnailUrl_fromDocument() {
        UUID userId = UUID.randomUUID();
        UUID docId = UUID.fromString("12345678-aaaa-bbbb-cccc-1234567890ab");

        Document doc = Document.builder()
                .id(docId).title("Brief").originalFilename("brief.pdf")
                .thumbnailKey("thumbnails/" + docId + ".jpg")
                .thumbnailGeneratedAt(LocalDateTime.of(2026, 4, 23, 9, 0, 0))
                .receivers(new HashSet<>())
                .build();

        when(auditLogQueryService.findMostRecentDocumentForUser(userId)).thenReturn(Optional.of(docId));
        when(documentService.getDocumentById(docId)).thenReturn(doc);
        when(transcriptionService.listBlocks(docId)).thenReturn(List.of());

        DashboardResumeDTO result = dashboardService.getResume(userId);

        assertThat(result).isNotNull();
        assertThat(result.thumbnailUrl()).isEqualTo(doc.getThumbnailUrl());
        assertThat(result.thumbnailUrl()).startsWith("/api/documents/" + docId + "/thumbnail?v=");
    }

    // ─── toActorDTO (via getResume collaborators) ─────────────────────────────

    @Test
    void getResume_collaboratorName_isNullSafe_whenFirstNameIsNull() {
        UUID userId = UUID.randomUUID();
        UUID docId = UUID.randomUUID();
        UUID collaboratorId = UUID.randomUUID();

        Document doc = Document.builder()
                .id(docId).title("Brief").originalFilename("brief.pdf")
                .receivers(new HashSet<>())
                .build();

        TranscriptionBlock block = TranscriptionBlock.builder()
                .id(UUID.randomUUID()).annotationId(UUID.randomUUID())
                .documentId(docId).sortOrder(1).updatedBy(collaboratorId)
                .build();

        AppUser collaborator = AppUser.builder()
                .id(collaboratorId).email("s@test.com").password("pw")
                .firstName(null).lastName("Schmidt").color("#abc")
                .build();

        when(auditLogQueryService.findMostRecentDocumentForUser(userId)).thenReturn(Optional.of(docId));
        when(documentService.getDocumentById(docId)).thenReturn(doc);
        when(transcriptionService.listBlocks(docId)).thenReturn(List.of(block));
        when(userService.getById(collaboratorId)).thenReturn(collaborator);

        DashboardResumeDTO result = dashboardService.getResume(userId);

        assertThat(result).isNotNull();
        assertThat(result.collaborators()).hasSize(1);
        assertThat(result.collaborators().get(0).name()).isEqualTo("Schmidt");
    }

    // ─── getActivity bulk-load ────────────────────────────────────────────────

    @Test
    void getActivity_loadsDocumentTitles_withoutPerRowLookup() {
        UUID userId = UUID.randomUUID();
        UUID docId = UUID.randomUUID();

        ActivityFeedRow row = mockFeedRow(docId, "ANNOTATION_CREATED");
        when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row, row));

        Document doc = Document.builder()
                .id(docId).title("Familienbrief").originalFilename("f.pdf")
                .receivers(new HashSet<>())
                .build();
        when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(doc));

        List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);

        assertThat(items).hasSize(2);
        assertThat(items.get(0).documentTitle()).isEqualTo("Familienbrief");
        verify(documentService, never()).getDocumentById(docId);
    }

    // ─── getActivity comment/annotation enrichment ────────────────────────────

    @Test
    void getActivity_populatesCommentId_forCommentEvents() {
        UUID userId = UUID.randomUUID();
        UUID docId = UUID.randomUUID();
        UUID commentId = UUID.randomUUID();

        ActivityFeedRow row = mockFeedRow(docId, "COMMENT_ADDED", commentId);
        when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row));
        when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
                Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
        ));
        when(commentService.findDataByIds(List.of(commentId)))
                .thenReturn(Map.of(commentId, new CommentData(null, "preview text")));

        List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);

        assertThat(items).hasSize(1);
        assertThat(items.get(0).commentId()).isEqualTo(commentId);
    }

    @Test
    void getActivity_populatesAnnotationId_viaCommentService() {
        UUID userId = UUID.randomUUID();
        UUID docId = UUID.randomUUID();
        UUID commentId = UUID.randomUUID();
        UUID annotationId = UUID.randomUUID();

        ActivityFeedRow row = mockFeedRow(docId, "COMMENT_ADDED", commentId);
        when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row));
        when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
                Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
        ));
        when(commentService.findDataByIds(List.of(commentId)))
                .thenReturn(Map.of(commentId, new CommentData(annotationId, "preview text")));

        List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);

        assertThat(items).hasSize(1);
        assertThat(items.get(0).annotationId()).isEqualTo(annotationId);
    }

    @Test
    void getActivity_leavesBothNull_forNonCommentKinds() {
        UUID userId = UUID.randomUUID();
        UUID docId = UUID.randomUUID();

        ActivityFeedRow row = mockFeedRow(docId, "TEXT_SAVED", null);
        when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row));
        when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
                Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
        ));

        List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);

        assertThat(items).hasSize(1);
        assertThat(items.get(0).commentId()).isNull();
        assertThat(items.get(0).annotationId()).isNull();
        verify(commentService, never()).findDataByIds(anyList());
    }

    // ─── getActivity commentPreview ───────────────────────────────────────────

    @Test
    void getActivity_populates_commentPreview_for_COMMENT_ADDED_rows() {
        UUID userId = UUID.randomUUID();
        UUID docId = UUID.randomUUID();
        UUID commentId = UUID.randomUUID();

        ActivityFeedRow row = mockFeedRow(docId, "COMMENT_ADDED", commentId);
        when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row));
        when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
                Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
        ));
        when(commentService.findDataByIds(List.of(commentId)))
                .thenReturn(Map.of(commentId, new CommentData(null, "Hello family!")));

        List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);

        assertThat(items.get(0).commentPreview()).isEqualTo("Hello family!");
    }

    @Test
    void getActivity_leaves_commentPreview_null_for_TEXT_SAVED_rows() {
        UUID userId = UUID.randomUUID();
        UUID docId = UUID.randomUUID();

        ActivityFeedRow row = mockFeedRow(docId, "TEXT_SAVED", null);
        when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row));
        when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
                Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
        ));

        List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);

        assertThat(items.get(0).commentPreview()).isNull();
    }

    @Test
    void getActivity_leaves_commentPreview_null_when_comment_is_deleted() {
        UUID userId = UUID.randomUUID();
        UUID docId = UUID.randomUUID();
        UUID deletedCommentId = UUID.randomUUID();

        ActivityFeedRow row = mockFeedRow(docId, "COMMENT_ADDED", deletedCommentId);
        when(auditLogQueryService.findActivityFeed(userId, 5, AuditKind.ROLLUP_ELIGIBLE)).thenReturn(List.of(row));
        when(documentService.getDocumentsByIds(List.of(docId))).thenReturn(List.of(
                Document.builder().id(docId).title("B").originalFilename("b.pdf").receivers(new HashSet<>()).build()
        ));
        when(commentService.findDataByIds(List.of(deletedCommentId))).thenReturn(Map.of());

        List<ActivityFeedItemDTO> items = dashboardService.getActivity(userId, 5, AuditKind.ROLLUP_ELIGIBLE);

        assertThat(items.get(0).commentPreview()).isNull();
    }

    // ─── getPulse — always uses full ROLLUP_ELIGIBLE set ─────────────────────

    @Test
    void pulse_uses_all_rollup_eligible_kinds_never_calls_kinds_filtered_overload() {
        UUID userId = UUID.randomUUID();
        PulseStatsRow stats = new PulseStatsRow() {
            public long getPages() { return 0; }
            public long getAnnotated() { return 0; }
            public long getTranscribed() { return 0; }
            public long getUploaded() { return 0; }
            public long getYourPages() { return 0; }
        };
        when(auditLogQueryService.getPulseStats(any(OffsetDateTime.class), any(UUID.class)))
                .thenReturn(stats);
        when(auditLogQueryService.findActivityFeed(userId, 50)).thenReturn(List.of());

        dashboardService.getPulse(userId);

        verify(auditLogQueryService).findActivityFeed(userId, 50);
        verify(auditLogQueryService, never()).findActivityFeed(any(UUID.class), anyInt(), any(Set.class));
    }

    private ActivityFeedRow mockFeedRow(UUID docId, String kind) {
        return mockFeedRow(docId, kind, null);
    }

    private ActivityFeedRow mockFeedRow(UUID docId, String kind, UUID commentId) {
        return new ActivityFeedRow() {
            public String getKind() { return kind; }
            public UUID getActorId() { return null; }
            public String getActorInitials() { return ""; }
            public String getActorColor() { return ""; }
            public String getActorName() { return ""; }
            public UUID getDocumentId() { return docId; }
            public Instant getHappenedAt() { return Instant.now(); }
            public boolean isYouMentioned() { return false; }
            public boolean isYouParticipated() { return false; }
            public int getCount() { return 1; }
            public Instant getHappenedAtUntil() { return null; }
            public UUID getCommentId() { return commentId; }
        };
    }
}
