package de.cannamanage.service;

import de.cannamanage.domain.entity.Notification;
import de.cannamanage.domain.entity.NotificationSend;
import de.cannamanage.domain.entity.NotificationSendRecipient;
import de.cannamanage.domain.enums.NotificationType;
import de.cannamanage.domain.enums.TargetType;
import de.cannamanage.service.repository.MemberRepository;
import de.cannamanage.service.repository.NotificationRepository;
import de.cannamanage.service.repository.NotificationSendRepository;
import de.cannamanage.service.repository.NotificationSendRecipientRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * Notification service — persists notifications and delivers in real-time via WebSocket.
 * Extended in Sprint 7 with broadcast and targeted send capabilities.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class NotificationService {

    private final NotificationRepository notificationRepository;
    private final NotificationSendRepository notificationSendRepository;
    private final NotificationSendRecipientRepository sendRecipientRepository;
    private final MemberRepository memberRepository;
    private final SimpMessagingTemplate messagingTemplate;
    private final NotificationDispatchService dispatchService;

    /**
     * Send a notification: persist to DB + push via WebSocket.
     */
    @Transactional
    public Notification sendNotification(UUID userId, NotificationType type, String title, String message, String link) {
        var notification = new Notification(userId, type, title, message, link);
        notification = notificationRepository.save(notification);

        // Push to user's WebSocket queue
        pushToWebSocket(notification);

        // Dispatch to additional channels (Web Push, FCM, Email) asynchronously
        dispatchService.dispatch(notification, userId);

        log.debug("Notification sent to user {}: {} - {}", userId, type, title);
        return notification;
    }

    /**
     * Send a broadcast notification to ALL active members in the current tenant.
     */
    @Transactional
    public NotificationSend sendBroadcast(String title, String message, String link, UUID authorId) {
        // Get all active member user IDs in the current tenant
        var memberUserIds = memberRepository.findAllActiveUserIds();

        return sendToRecipients(title, message, link, authorId, memberUserIds, TargetType.ALL);
    }

    /**
     * Send a targeted notification to selected members.
     */
    @Transactional
    public NotificationSend sendToSelected(String title, String message, String link, UUID authorId, List<UUID> recipientIds) {
        return sendToRecipients(title, message, link, authorId, recipientIds, TargetType.SELECTED);
    }

    private NotificationSend sendToRecipients(String title, String message, String link, UUID authorId,
                                               List<UUID> recipientIds, TargetType targetType) {
        // Create the send record
        var send = NotificationSend.builder()
                .title(title)
                .message(message)
                .link(link)
                .authorId(authorId)
                .targetType(targetType)
                .targetCount(recipientIds.size())
                .readCount(0)
                .sentAt(Instant.now())
                .build();
        send = notificationSendRepository.save(send);

        // Create individual notifications for each recipient
        var recipients = new ArrayList<NotificationSendRecipient>();
        for (UUID userId : recipientIds) {
            var notification = new Notification(userId, NotificationType.ADMIN_MESSAGE, title, message, link);
            notification = notificationRepository.save(notification);

            recipients.add(NotificationSendRecipient.builder()
                    .sendId(send.getId())
                    .userId(userId)
                    .notificationId(notification.getId())
                    .build());

            // Push via WebSocket
            pushToWebSocket(notification);

            // Dispatch to push channels asynchronously
            dispatchService.dispatch(notification, userId);
        }

        sendRecipientRepository.saveAll(recipients);

        log.info("Broadcast sent: {} recipients, type={}, title='{}'", recipientIds.size(), targetType, title);
        return send;
    }

    private void pushToWebSocket(Notification notification) {
        messagingTemplate.convertAndSendToUser(
                notification.getUserId().toString(),
                "/queue/notifications",
                Map.of(
                        "id", notification.getId(),
                        "type", notification.getType().name(),
                        "title", notification.getTitle(),
                        "message", notification.getMessage(),
                        "link", notification.getLink() != null ? notification.getLink() : "",
                        "read", notification.isRead(),
                        "createdAt", notification.getCreatedAt().toString()
                )
        );
    }

    /**
     * Get the last 10 notifications for a user (unread first).
     */
    @Transactional(readOnly = true)
    public List<Notification> getRecentNotifications(UUID userId) {
        return notificationRepository.findTop10ByUserIdOrderByReadAscCreatedAtDesc(userId);
    }

    /**
     * Get unread count for badge display.
     */
    @Transactional(readOnly = true)
    public long getUnreadCount(UUID userId) {
        return notificationRepository.countByUserIdAndReadFalse(userId);
    }

    /**
     * Mark a single notification as read.
     */
    @Transactional
    public void markAsRead(UUID notificationId) {
        notificationRepository.findById(notificationId).ifPresent(n -> {
            n.setRead(true);
            notificationRepository.save(n);
        });
    }

    /**
     * Mark all notifications as read for a user.
     */
    @Transactional
    public int markAllAsRead(UUID userId) {
        return notificationRepository.markAllAsRead(userId);
    }
}
