package org.raddatz.familienarchiv.dashboard;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.raddatz.familienarchiv.audit.ActivityActorDTO;
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.person.Person;
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 org.springframework.stereotype.Service;

import java.time.DayOfWeek;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Stream;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class DashboardService {

    private final AuditLogQueryService auditLogQueryService;
    private final DocumentService documentService;
    private final TranscriptionService transcriptionService;
    private final UserService userService;
    private final CommentService commentService;

    public DashboardResumeDTO getResume(UUID userId) {
        Optional<UUID> docIdOpt = auditLogQueryService.findMostRecentDocumentForUser(userId);
        if (docIdOpt.isEmpty()) return null;

        UUID docId = docIdOpt.get();
        Document doc;
        try {
            doc = documentService.getDocumentById(docId);
        } catch (Exception e) {
            log.warn("Resume: document {} not found for user {}", docId, userId);
            return null;
        }

        List<TranscriptionBlock> blocks = transcriptionService.listBlocks(docId);
        String excerpt = blocks.stream()
                .filter(b -> b.getText() != null && !b.getText().isBlank())
                .min(Comparator.comparingInt(TranscriptionBlock::getSortOrder))
                .map(b -> b.getText().length() > 200 ? b.getText().substring(0, 200) + "…" : b.getText())
                .orElse("");

        int totalBlocks = blocks.size();
        long reviewedBlocks = blocks.stream().filter(TranscriptionBlock::isReviewed).count();
        int pct = totalBlocks > 0 ? (int) (reviewedBlocks * 100L / totalBlocks) : 0;

        String caption = buildCaption(doc);

        List<UUID> collaboratorIds = blocks.stream()
                .map(TranscriptionBlock::getUpdatedBy)
                .filter(Objects::nonNull)
                .distinct()
                .limit(5)
                .toList();

        List<ActivityActorDTO> collaborators = collaboratorIds.stream()
                .map(uid -> {
                    try {
                        AppUser u = userService.getById(uid);
                        return toActorDTO(u);
                    } catch (Exception e) {
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .toList();

        return new DashboardResumeDTO(docId, doc.getTitle(), caption, excerpt,
                totalBlocks, pct, doc.getThumbnailUrl(), collaborators);
    }

    public DashboardPulseDTO getPulse(UUID userId) {
        OffsetDateTime weekStart = OffsetDateTime.now(ZoneOffset.UTC)
                .with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
                .withHour(0).withMinute(0).withSecond(0).withNano(0);

        PulseStatsRow stats = auditLogQueryService.getPulseStats(weekStart, userId);

        List<ActivityFeedRow> feed = auditLogQueryService.findActivityFeed(userId, 50);
        List<ActivityActorDTO> contributors = feed.stream()
                .filter(r -> r.getActorId() != null)
                .map(r -> new ActivityActorDTO(r.getActorInitials(), r.getActorColor(), r.getActorName()))
                .filter(a -> !a.initials().isBlank())
                .distinct()
                .limit(6)
                .toList();

        return new DashboardPulseDTO(
                (int) stats.getPages(),
                (int) stats.getAnnotated(),
                (int) stats.getTranscribed(),
                (int) stats.getUploaded(),
                (int) stats.getYourPages(),
                contributors
        );
    }

    public List<ActivityFeedItemDTO> getActivity(UUID currentUserId, int limit, Set<AuditKind> kinds) {
        List<ActivityFeedRow> rows = auditLogQueryService.findActivityFeed(currentUserId, limit, kinds);

        List<UUID> docIds = rows.stream()
                .map(ActivityFeedRow::getDocumentId)
                .filter(Objects::nonNull)
                .distinct()
                .toList();

        Map<UUID, String> titleCache = new HashMap<>();
        try {
            documentService.getDocumentsByIds(docIds)
                    .forEach(d -> titleCache.put(d.getId(), d.getTitle()));
        } catch (Exception e) {
            log.warn("Activity: failed to bulk-load document titles", e);
        }

        List<UUID> commentIds = rows.stream()
                .map(ActivityFeedRow::getCommentId)
                .filter(Objects::nonNull)
                .distinct()
                .toList();
        Map<UUID, CommentData> commentDataByComment = commentIds.isEmpty()
                ? Map.of()
                : commentService.findDataByIds(commentIds);

        return rows.stream().map(row -> {
            ActivityActorDTO actor = row.getActorId() != null
                    ? new ActivityActorDTO(row.getActorInitials(), row.getActorColor(), row.getActorName())
                    : null;
            String docTitle = titleCache.getOrDefault(row.getDocumentId(), "");
            OffsetDateTime happenedAtUntil = row.getHappenedAtUntil() != null
                    ? row.getHappenedAtUntil().atOffset(ZoneOffset.UTC)
                    : null;
            UUID commentId = row.getCommentId();
            CommentData commentData = commentId != null ? commentDataByComment.get(commentId) : null;
            UUID annotationId = commentData != null ? commentData.annotationId() : null;
            String commentPreview = commentData != null && !commentData.preview().isBlank()
                    ? commentData.preview() : null;
            return new ActivityFeedItemDTO(
                    org.raddatz.familienarchiv.audit.AuditKind.valueOf(row.getKind()),
                    actor,
                    row.getDocumentId(),
                    docTitle,
                    row.getHappenedAt().atOffset(ZoneOffset.UTC),
                    row.isYouMentioned(),
                    row.isYouParticipated(),
                    row.getCount(),
                    happenedAtUntil,
                    commentId,
                    annotationId,
                    commentPreview
            );
        }).toList();
    }

    private String buildCaption(Document doc) {
        StringBuilder sb = new StringBuilder();
        if (doc.getSender() != null) sb.append(personName(doc.getSender()));
        if (!doc.getReceivers().isEmpty()) {
            String receivers = doc.getReceivers().stream()
                    .map(this::personName).collect(Collectors.joining(", "));
            if (!sb.isEmpty()) sb.append(" an ");
            sb.append(receivers);
        }
        if (doc.getDocumentDate() != null) {
            if (!sb.isEmpty()) sb.append(" · ");
            sb.append(doc.getDocumentDate());
        }
        return sb.toString();
    }

    private String personName(Person p) {
        if (p == null) return "";
        if (p.getFirstName() != null && p.getLastName() != null) return p.getFirstName() + " " + p.getLastName();
        if (p.getFirstName() != null) return p.getFirstName();
        if (p.getLastName() != null) return p.getLastName();
        return "";
    }

    private ActivityActorDTO toActorDTO(AppUser u) {
        String initials = "";
        if (u.getFirstName() != null && !u.getFirstName().isBlank())
            initials += u.getFirstName().charAt(0);
        if (u.getLastName() != null && !u.getLastName().isBlank())
            initials += u.getLastName().charAt(0);
        if (initials.isBlank() && u.getEmail() != null)
            initials = u.getEmail().substring(0, 1).toUpperCase();
        String fullName = Stream.of(u.getFirstName(), u.getLastName())
                .filter(Objects::nonNull)
                .collect(Collectors.joining(" "));
        return new ActivityActorDTO(initials.toUpperCase(), u.getColor(), fullName);
    }
}
