package com.unicorn.hgzero.notification.service;

import com.unicorn.hgzero.notification.domain.Notification;
import com.unicorn.hgzero.notification.domain.NotificationRecipient;
import com.unicorn.hgzero.notification.domain.NotificationSetting;
import com.unicorn.hgzero.notification.event.event.MeetingCreatedEvent;
import com.unicorn.hgzero.notification.event.event.TodoAssignedEvent;
import com.unicorn.hgzero.notification.repository.NotificationRecipientRepository;
import com.unicorn.hgzero.notification.repository.NotificationRepository;
import com.unicorn.hgzero.notification.repository.NotificationSettingRepository;
import jakarta.mail.MessagingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * 알림 발송 비즈니스 로직 서비스
 *
 * 회의 초대, Todo 할당 등 다양한 알림 발송 처리
 * 중복 방지, 사용자 설정 확인, 재시도 관리 포함
 *
 * @author 준호 (Backend Developer)
 * @version 1.0
 * @since 2025-10-23
 */
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional
public class NotificationService {

    private final NotificationRepository notificationRepository;
    private final NotificationRecipientRepository recipientRepository;
    private final NotificationSettingRepository settingRepository;
    private final EmailTemplateService templateService;
    private final EmailClient emailClient;

    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

    /**
     * 회의 초대 알림 발송
     *
     * @param event 회의 생성 이벤트
     */
    public void sendMeetingInvitation(MeetingCreatedEvent event) {
        log.info("회의 초대 알림 처리 시작 - MeetingId: {}, EventId: {}",
            event.getMeetingId(), event.getEventId());

        // 1. 중복 발송 방지 체크
        if (notificationRepository.existsByEventId(event.getEventId())) {
            log.warn("이미 처리된 이벤트 - EventId: {}", event.getEventId());
            return;
        }

        // 2. 알림 엔티티 생성
        Notification notification = Notification.builder()
            .eventId(event.getEventId())
            .referenceId(event.getMeetingId())
            .referenceType(Notification.ReferenceType.MEETING)
            .notificationType(Notification.NotificationType.INVITATION)
            .title("회의 초대: " + event.getTitle())
            .message(event.getDescription())
            .status(Notification.NotificationStatus.PROCESSING)
            .channel(Notification.NotificationChannel.EMAIL)
            .build();

        notificationRepository.save(notification);

        // 3. 각 참석자에게 알림 발송
        int successCount = 0;
        int failureCount = 0;

        for (MeetingCreatedEvent.ParticipantInfo participant : event.getParticipants()) {
            try {
                // 3-1. 알림 설정 확인
                if (!canSendNotification(participant.getUserId(), Notification.NotificationType.INVITATION)) {
                    log.info("알림 설정에 의해 발송 제외 - UserId: {}", participant.getUserId());
                    continue;
                }

                // 3-2. 수신자 엔티티 생성
                NotificationRecipient recipient = NotificationRecipient.builder()
                    .recipientUserId(participant.getUserId())
                    .recipientName(participant.getName())
                    .recipientEmail(participant.getEmail())
                    .status(NotificationRecipient.RecipientStatus.PENDING)
                    .build();

                notification.addRecipient(recipient);

                // 3-3. 이메일 템플릿 렌더링
                Map<String, Object> variables = new HashMap<>();
                variables.put("title", event.getTitle());
                variables.put("description", event.getDescription());
                variables.put("startTime", event.getStartTime().format(DATETIME_FORMATTER));
                variables.put("endTime", event.getEndTime().format(DATETIME_FORMATTER));
                variables.put("location", event.getLocation());
                variables.put("organizerName", event.getOrganizer().getName());
                variables.put("participantName", participant.getName());

                String htmlContent = templateService.renderMeetingInvitation(variables);

                // 3-4. 이메일 발송
                emailClient.sendHtmlEmail(
                    participant.getEmail(),
                    "회의 초대: " + event.getTitle(),
                    htmlContent
                );

                // 3-5. 발송 성공 처리
                recipient.markAsSent();
                notification.incrementSentCount();
                successCount++;

                log.info("회의 초대 알림 발송 성공 - Email: {}", participant.getEmail());

            } catch (MessagingException e) {
                // 3-6. 발송 실패 처리
                NotificationRecipient recipient = notification.getRecipients().stream()
                    .filter(r -> r.getRecipientUserId().equals(participant.getUserId()))
                    .findFirst()
                    .orElse(null);

                if (recipient != null) {
                    recipient.markAsFailed(e.getMessage());
                    notification.incrementFailedCount();
                }

                failureCount++;
                log.error("회의 초대 알림 발송 실패 - Email: {}", participant.getEmail(), e);
            }
        }

        // 4. 알림 상태 업데이트
        if (successCount > 0 && failureCount == 0) {
            notification.updateStatus(Notification.NotificationStatus.SENT);
        } else if (successCount > 0 && failureCount > 0) {
            notification.updateStatus(Notification.NotificationStatus.PARTIAL);
        } else {
            notification.updateStatus(Notification.NotificationStatus.FAILED);
        }

        notificationRepository.save(notification);

        log.info("회의 초대 알림 처리 완료 - 성공: {}, 실패: {}", successCount, failureCount);
    }

    /**
     * Todo 할당 알림 발송
     *
     * @param event Todo 할당 이벤트
     */
    public void sendTodoAssignment(TodoAssignedEvent event) {
        log.info("Todo 할당 알림 처리 시작 - TodoId: {}, EventId: {}",
            event.getTodoId(), event.getEventId());

        // 1. 중복 발송 방지 체크
        if (notificationRepository.existsByEventId(event.getEventId())) {
            log.warn("이미 처리된 이벤트 - EventId: {}", event.getEventId());
            return;
        }

        // 2. 알림 엔티티 생성
        Notification notification = Notification.builder()
            .eventId(event.getEventId())
            .referenceId(event.getTodoId())
            .referenceType(Notification.ReferenceType.TODO)
            .notificationType(Notification.NotificationType.TODO_ASSIGNED)
            .title("Todo 할당: " + event.getTitle())
            .message(event.getDescription())
            .status(Notification.NotificationStatus.PROCESSING)
            .channel(Notification.NotificationChannel.EMAIL)
            .build();

        notificationRepository.save(notification);

        try {
            // 3. 알림 설정 확인
            TodoAssignedEvent.UserInfo assignee = event.getAssignee();

            if (!canSendNotification(assignee.getUserId(), Notification.NotificationType.TODO_ASSIGNED)) {
                log.info("알림 설정에 의해 발송 제외 - UserId: {}", assignee.getUserId());
                notification.updateStatus(Notification.NotificationStatus.SENT);
                return;
            }

            // 4. 수신자 엔티티 생성
            NotificationRecipient recipient = NotificationRecipient.builder()
                .recipientUserId(assignee.getUserId())
                .recipientName(assignee.getName())
                .recipientEmail(assignee.getEmail())
                .status(NotificationRecipient.RecipientStatus.PENDING)
                .build();

            notification.addRecipient(recipient);

            // 5. 이메일 템플릿 렌더링
            Map<String, Object> variables = new HashMap<>();
            variables.put("title", event.getTitle());
            variables.put("description", event.getDescription());
            variables.put("deadline", event.getDeadline().format(DATETIME_FORMATTER));
            variables.put("priority", event.getPriority());
            variables.put("assignedByName", event.getAssignedBy().getName());
            variables.put("assigneeName", assignee.getName());

            // 회의 관련 Todo인 경우 회의 정보 추가
            if (event.getMeetingId() != null) {
                variables.put("meetingId", event.getMeetingId());
            }

            String htmlContent = templateService.renderTodoAssigned(variables);

            // 6. 이메일 발송
            emailClient.sendHtmlEmail(
                assignee.getEmail(),
                "Todo 할당: " + event.getTitle(),
                htmlContent
            );

            // 7. 발송 성공 처리
            recipient.markAsSent();
            notification.incrementSentCount();
            notification.updateStatus(Notification.NotificationStatus.SENT);

            log.info("Todo 할당 알림 발송 성공 - Email: {}", assignee.getEmail());

        } catch (MessagingException e) {
            // 8. 발송 실패 처리
            NotificationRecipient recipient = notification.getRecipients().stream()
                .findFirst()
                .orElse(null);

            if (recipient != null) {
                recipient.markAsFailed(e.getMessage());
            }

            notification.incrementFailedCount();
            notification.updateStatus(Notification.NotificationStatus.FAILED);

            log.error("Todo 할당 알림 발송 실패", e);
        }

        notificationRepository.save(notification);
        log.info("Todo 할당 알림 처리 완료");
    }

    /**
     * 알림 발송 가능 여부 확인
     *
     * 사용자 알림 설정, 채널, 알림 유형, 방해 금지 시간대 체크
     *
     * @param userId 사용자 ID
     * @param notificationType 알림 유형
     * @return 발송 가능 여부
     */
    private boolean canSendNotification(String userId, Notification.NotificationType notificationType) {
        Optional<NotificationSetting> settingOpt = settingRepository.findByUserId(userId);

        // 설정이 없으면 기본값으로 발송 허용 (이메일, 초대/할당 알림만)
        if (settingOpt.isEmpty()) {
            return notificationType == Notification.NotificationType.INVITATION
                || notificationType == Notification.NotificationType.TODO_ASSIGNED;
        }

        NotificationSetting setting = settingOpt.get();

        // 이메일 채널 및 알림 유형 활성화 여부 확인
        return setting.canSendNotification(notificationType, Notification.NotificationChannel.EMAIL);
    }
}
