package org.raddatz.familienarchiv.notification;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.raddatz.familienarchiv.notification.NotificationDTO;
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.DocumentService;
import org.raddatz.familienarchiv.notification.SseEmitterRegistry;
import org.raddatz.familienarchiv.user.UserService;
import org.raddatz.familienarchiv.notification.Notification;
import org.raddatz.familienarchiv.notification.NotificationType;
import org.raddatz.familienarchiv.notification.NotificationRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class NotificationService {

    private final NotificationRepository notificationRepository;
    private final UserService userService;
    private final DocumentService documentService;
    private final Optional<JavaMailSender> mailSender;
    private final SseEmitterRegistry sseEmitterRegistry;

    @Value("${app.mail.from:noreply@familienarchiv.local}")
    private String mailFrom;

    @Value("${app.base-url:http://localhost:3000}")
    private String baseUrl;

    /**
     * Creates REPLY notifications for all participants in the thread, excluding the replier.
     * Runs in a separate transaction so a notification failure cannot roll back the parent comment.
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void notifyReply(DocumentComment reply, Set<UUID> participantIds) {
        if (participantIds.isEmpty()) return;

        List<AppUser> recipients = userService.findAllById(participantIds);
        for (AppUser recipient : recipients) {
            Notification notification = Notification.builder()
                    .recipient(recipient)
                    .type(NotificationType.REPLY)
                    .documentId(reply.getDocumentId())
                    .referenceId(reply.getId())
                    .annotationId(reply.getAnnotationId())
                    .actorName(reply.getAuthorName())
                    .build();
            saveAndPush(notification);

            if (recipient.isNotifyOnReply()) {
                sendNotificationEmail(recipient, reply, NotificationType.REPLY);
            }
        }
    }

    /**
     * Creates MENTION notifications for each mentioned user.
     * Runs in a separate transaction so a notification failure cannot roll back the parent comment.
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void notifyMentions(List<UUID> mentionedUserIds, DocumentComment comment) {
        if (mentionedUserIds == null || mentionedUserIds.isEmpty()) return;

        List<AppUser> recipients = userService.findAllById(mentionedUserIds);
        for (AppUser recipient : recipients) {
            Notification notification = Notification.builder()
                    .recipient(recipient)
                    .type(NotificationType.MENTION)
                    .documentId(comment.getDocumentId())
                    .referenceId(comment.getId())
                    .annotationId(comment.getAnnotationId())
                    .actorName(comment.getAuthorName())
                    .build();
            saveAndPush(notification);

            if (recipient.isNotifyOnMention()) {
                sendNotificationEmail(recipient, comment, NotificationType.MENTION);
            }
        }
    }

    public Page<NotificationDTO> getNotifications(UUID userId, NotificationType type, Boolean read, Pageable pageable) {
        Page<Notification> page;
        if (type != null && Boolean.FALSE.equals(read)) {
            page = notificationRepository.findByRecipientIdAndTypeAndReadFalseOrderByCreatedAtDesc(userId, type, pageable);
        } else if (type != null) {
            page = notificationRepository.findByRecipientIdAndTypeOrderByCreatedAtDesc(userId, type, pageable);
        } else if (Boolean.FALSE.equals(read)) {
            page = notificationRepository.findByRecipientIdAndReadFalseOrderByCreatedAtDesc(userId, pageable);
        } else {
            page = notificationRepository.findByRecipientIdOrderByCreatedAtDesc(userId, pageable);
        }
        return mapWithDocumentTitles(page);
    }

    private Page<NotificationDTO> mapWithDocumentTitles(Page<Notification> page) {
        Set<UUID> documentIds = page.getContent().stream()
                .map(Notification::getDocumentId)
                .filter(id -> id != null)
                .collect(Collectors.toSet());
        Map<UUID, String> titles = documentService.findTitlesByIds(documentIds);
        return page.map(n -> toDTO(n, titles));
    }

    public long countUnread(UUID userId) {
        return notificationRepository.countByRecipientIdAndReadFalse(userId);
    }

    @Transactional
    public void markAllRead(UUID userId) {
        notificationRepository.markAllReadByRecipientId(userId);
    }

    @Transactional
    public NotificationDTO markRead(UUID notificationId, UUID userId) {
        Notification notification = notificationRepository.findById(notificationId)
                .orElseThrow(() -> DomainException.notFound(
                        ErrorCode.NOTIFICATION_NOT_FOUND, "Notification not found: " + notificationId));
        if (!notification.getRecipient().getId().equals(userId)) {
            throw DomainException.forbidden("Notification belongs to a different user");
        }
        notification.setRead(true);
        return toDTO(notificationRepository.save(notification), Map.of());
    }

    @Transactional
    public AppUser updatePreferences(UUID userId, boolean notifyOnReply, boolean notifyOnMention) {
        return userService.updateNotificationPreferences(userId, notifyOnReply, notifyOnMention);
    }

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

    private void saveAndPush(Notification notification) {
        Notification saved = notificationRepository.save(notification);
        sseEmitterRegistry.send(saved.getRecipient().getId(), toDTO(saved, Map.of()));
    }

    private NotificationDTO toDTO(Notification n, Map<UUID, String> titles) {
        return new NotificationDTO(
                n.getId(),
                n.getType(),
                n.getDocumentId(),
                n.getReferenceId(),
                n.getAnnotationId(),
                n.isRead(),
                n.getCreatedAt(),
                n.getActorName(),
                n.getDocumentId() != null ? titles.get(n.getDocumentId()) : null
        );
    }

    private void buildCommentPath(DocumentComment comment, StringBuilder sb) {
        sb.append("?commentId=").append(comment.getId());
        if (comment.getAnnotationId() != null) {
            sb.append("&annotationId=").append(comment.getAnnotationId());
        }
    }

    private void sendNotificationEmail(AppUser recipient, DocumentComment comment, NotificationType type) {
        if (mailSender.isEmpty()) {
            log.warn("Mail sender not configured — skipping notification email to {}", recipient.getEmail());
            return;
        }
        if (recipient.getEmail() == null || recipient.getEmail().isBlank()) return;

        StringBuilder path = new StringBuilder("/documents/").append(comment.getDocumentId());
        buildCommentPath(comment, path);
        String link = baseUrl + path;

        String subject = type == NotificationType.REPLY
                ? "Neue Antwort auf deinen Kommentar — Familienarchiv"
                : "Du wurdest in einem Kommentar erwähnt — Familienarchiv";

        String body = type == NotificationType.REPLY
                ? "Hallo,\n\njemand hat auf einen Kommentar geantwortet, an dem du beteiligt warst.\n\n"
                + "Zum Kommentar:\n" + link + "\n\nDein Familienarchiv-Team"
                : "Hallo,\n\njemand hat dich in einem Kommentar erwähnt.\n\n"
                + "Zum Kommentar:\n" + link + "\n\nDein Familienarchiv-Team";

        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(mailFrom);
        message.setTo(recipient.getEmail());
        message.setSubject(subject);
        message.setText(body);

        try {
            mailSender.get().send(message);
        } catch (MailException e) {
            log.error("Failed to send notification email to {}: {}", recipient.getEmail(), e.getMessage());
        }
    }
}
