package org.raddatz.familienarchiv.document.comment;

import lombok.RequiredArgsConstructor;
import org.raddatz.familienarchiv.audit.AuditKind;
import org.raddatz.familienarchiv.audit.AuditService;
import org.raddatz.familienarchiv.document.transcription.MentionDTO;
import org.raddatz.familienarchiv.document.transcription.TranscriptionService;
import org.raddatz.familienarchiv.user.UserService;
import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.user.AppUser;
import org.raddatz.familienarchiv.document.comment.DocumentComment;
import org.raddatz.familienarchiv.document.transcription.TranscriptionBlock;
import org.raddatz.familienarchiv.document.comment.CommentRepository;
import org.raddatz.familienarchiv.notification.NotificationService;
import org.jsoup.Jsoup;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

@Service
@RequiredArgsConstructor
public class CommentService {

    private static final int PREVIEW_MAX_CHARS = 120;

    private final CommentRepository commentRepository;
    private final UserService userService;
    private final NotificationService notificationService;
    private final AuditService auditService;
    private final TranscriptionService transcriptionService;

    public Map<UUID, CommentData> findDataByIds(Collection<UUID> commentIds) {
        if (commentIds == null || commentIds.isEmpty()) return Map.of();
        Map<UUID, CommentData> result = new HashMap<>();
        for (DocumentComment c : commentRepository.findAllById(commentIds)) {
            result.put(c.getId(), new CommentData(c.getAnnotationId(), stripAndTruncate(c.getContent())));
        }
        return result;
    }

    private String stripAndTruncate(String html) {
        if (html == null || html.isBlank()) return "";
        String text = Jsoup.parse(html).text().trim();
        return text.length() > PREVIEW_MAX_CHARS ? text.substring(0, PREVIEW_MAX_CHARS) : text;
    }

    public List<DocumentComment> getCommentsForBlock(UUID blockId) {
        List<DocumentComment> roots = commentRepository.findByBlockIdAndParentIdIsNull(blockId);
        return withRepliesAndMentions(roots);
    }

    @Transactional
    public DocumentComment postBlockComment(UUID documentId, UUID blockId, String content,
                                            List<UUID> mentionedUserIds, AppUser author) {
        TranscriptionBlock block = transcriptionService.getBlock(documentId, blockId);
        DocumentComment comment = DocumentComment.builder()
                .documentId(documentId)
                .blockId(blockId)
                .annotationId(block.getAnnotationId())
                .content(content)
                .authorId(author.getId())
                .authorName(resolveAuthorName(author))
                .build();
        saveMentions(comment, mentionedUserIds);
        DocumentComment saved = commentRepository.save(comment);
        withMentionDTOs(saved);
        notificationService.notifyMentions(mentionedUserIds, saved);
        logCommentPosted(author, documentId, saved, mentionedUserIds);
        return saved;
    }

    @Transactional
    public DocumentComment replyToComment(UUID documentId, UUID commentId, String content,
                                          List<UUID> mentionedUserIds, AppUser author) {
        DocumentComment target = commentRepository.findById(commentId)
                .orElseThrow(() -> DomainException.notFound(
                        ErrorCode.COMMENT_NOT_FOUND, "Comment not found: " + commentId));

        UUID rootId = target.getParentId() != null ? target.getParentId() : target.getId();
        DocumentComment root = commentRepository.findById(rootId)
                .orElseThrow(() -> DomainException.notFound(
                        ErrorCode.COMMENT_NOT_FOUND, "Comment not found: " + rootId));

        DocumentComment reply = DocumentComment.builder()
                .documentId(documentId)
                .annotationId(root.getAnnotationId())
                .parentId(root.getId())
                .content(content)
                .authorId(author.getId())
                .authorName(resolveAuthorName(author))
                .build();
        saveMentions(reply, mentionedUserIds);
        DocumentComment saved = commentRepository.save(reply);
        withMentionDTOs(saved);

        Set<UUID> participantIds = collectParticipantIds(root);
        participantIds.remove(author.getId());
        notificationService.notifyReply(saved, participantIds);
        notificationService.notifyMentions(mentionedUserIds, saved);
        logCommentPosted(author, documentId, saved, mentionedUserIds);
        return saved;
    }

    @Transactional
    public DocumentComment editComment(UUID documentId, UUID commentId, String content, AppUser currentUser) {
        DocumentComment comment = findComment(documentId, commentId);
        if (!currentUser.getId().equals(comment.getAuthorId())) {
            throw DomainException.forbidden("Only the comment author can edit it");
        }
        comment.setContent(content);
        return commentRepository.save(comment);
    }

    @Transactional
    public void deleteComment(UUID documentId, UUID commentId, AppUser currentUser) {
        DocumentComment comment = findComment(documentId, commentId);
        boolean isAuthor = currentUser.getId().equals(comment.getAuthorId());
        boolean isAdmin = currentUser.hasPermission("ADMIN");
        if (!isAuthor && !isAdmin) {
            throw DomainException.forbidden("Only the comment author or an admin can delete it");
        }
        commentRepository.delete(comment);
    }

    public List<DocumentComment> findReplies(UUID parentId) {
        return commentRepository.findByParentId(parentId);
    }

    // ─── private helpers ──────────────────────────────────────────────────────

    private List<DocumentComment> withRepliesAndMentions(List<DocumentComment> roots) {
        roots.forEach(root -> {
            List<DocumentComment> replies = commentRepository.findByParentId(root.getId());
            replies.forEach(this::withMentionDTOs);
            root.setReplies(replies);
            withMentionDTOs(root);
        });
        return roots;
    }

    private void saveMentions(DocumentComment comment, List<UUID> mentionedUserIds) {
        if (mentionedUserIds == null || mentionedUserIds.isEmpty()) return;
        List<AppUser> users = userService.findAllById(mentionedUserIds);
        comment.setMentions(users);
    }

    private void withMentionDTOs(DocumentComment comment) {
        List<MentionDTO> dtos = comment.getMentions().stream()
                .map(u -> new MentionDTO(u.getId(), u.getFirstName(), u.getLastName()))
                .toList();
        comment.setMentionDTOs(dtos);
    }

    private Set<UUID> collectParticipantIds(DocumentComment root) {
        Set<UUID> ids = new LinkedHashSet<>();
        if (root.getAuthorId() != null) ids.add(root.getAuthorId());
        commentRepository.findByParentId(root.getId())
                .forEach(reply -> {
                    if (reply.getAuthorId() != null) ids.add(reply.getAuthorId());
                });
        return ids;
    }

    private DocumentComment findComment(UUID documentId, UUID commentId) {
        return commentRepository.findById(commentId)
                .filter(c -> documentId.equals(c.getDocumentId()))
                .orElseThrow(() -> DomainException.notFound(
                        ErrorCode.COMMENT_NOT_FOUND, "Comment not found: " + commentId));
    }

    private void logCommentPosted(AppUser author, UUID documentId, DocumentComment saved, List<UUID> mentionedUserIds) {
        UUID actorId = author != null ? author.getId() : null;
        String commentId = saved.getId().toString();
        auditService.logAfterCommit(AuditKind.COMMENT_ADDED, actorId, documentId, Map.of("commentId", commentId));
        if (mentionedUserIds != null) {
            mentionedUserIds.forEach(mentionedUserId ->
                    auditService.logAfterCommit(AuditKind.MENTION_CREATED, actorId, documentId,
                            Map.of("commentId", commentId, "mentionedUserId", mentionedUserId.toString())));
        }
    }

    private String resolveAuthorName(AppUser author) {
        String first = author.getFirstName();
        String last = author.getLastName();
        if ((first == null || first.isBlank()) && (last == null || last.isBlank())) {
            return author.getEmail();
        }
        return ((first != null ? first : "") + " " + (last != null ? last : "")).strip();
    }
}
