package com.zioinfo.kintex.mail;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.UUID;

/**
 * 메일 발송 서비스 — 자체 Postfix SMTP(mail.zioinfo.co.kr) 채널.
 *
 * <p><b>불변 규칙</b>:
 * <ul>
 *   <li>SMTP 자격증명은 {@code spring.mail.*}(env)로만 로드 — 코드·응답·로그 미기재(§0-3).</li>
 *   <li>실패 격리: 발송 실패는 호출 기능(옥션 개설·비번 재설정 등)으로 전파하지 않는다. 1회 재시도 후 이력만 남긴다.</li>
 *   <li>수신자는 마스킹(kim***@do.com)만 로그·이력에 기록. 본문은 저장하지 않는다.</li>
 *   <li>{@code MAIL_ENABLED=false}면 실제 전송 없이 skipped 이력만 남긴다(정직 로그).</li>
 * </ul>
 *
 * <p>@Async 로 호출 스레드를 차단하지 않는다(대량 EDM 발송 시 요청 응답 지연 방지).
 */
@Service
public class MailService {

    private static final Logger log = LoggerFactory.getLogger(MailService.class);

    private final MailProperties props;
    private final ObjectProvider<JavaMailSender> mailSenderProvider;
    private final MailLogMapper mailLogMapper;

    public MailService(MailProperties props,
                       ObjectProvider<JavaMailSender> mailSenderProvider,
                       MailLogMapper mailLogMapper) {
        this.props = props;
        this.mailSenderProvider = mailSenderProvider;
        this.mailLogMapper = mailLogMapper;
    }

    /**
     * 단건 HTML 메일 발송(비동기·실패 격리). category/refId 는 이력 분류용, to 는 발송 경로 한정 원문 수신자.
     * MAIL_ENABLED=false 또는 JavaMailSender 미구성 시 실제 전송 없이 skipped 이력만 남긴다.
     */
    @Async("mailExecutor")
    public void send(String category, String refId, String to, String subject, String htmlBody) {
        String masked = maskEmail(to);
        if (to == null || to.isBlank()) {
            record(category, refId, subject, masked, "skipped", "빈 수신자");
            return;
        }
        JavaMailSender sender = mailSenderProvider.getIfAvailable();
        if (!props.isEnabled() || sender == null) {
            // 실 발송 비활성 — 스킵 이력만(원문·자격증명 미기록).
            log.info("메일 발송 스킵(MAIL_ENABLED=false){}: cat={} to={} subj={}",
                    sender == null ? "/sender-absent" : "", category, masked, subject);
            record(category, refId, subject, masked, "skipped", "MAIL_ENABLED=false");
            return;
        }
        // 실 발송 — 1회 재시도. 최종 실패도 예외 전파 없이 이력만.
        RuntimeException last = null;
        for (int attempt = 1; attempt <= 2; attempt++) {
            try {
                var message = sender.createMimeMessage();
                MimeMessageHelper helper = new MimeMessageHelper(message, false, StandardCharsets.UTF_8.name());
                helper.setFrom(props.getFrom());
                helper.setTo(to);
                helper.setSubject(subject);
                helper.setText(htmlBody, true);
                sender.send(message);
                record(category, refId, subject, masked, "sent", null);
                return;
            } catch (Exception e) {
                last = e instanceof RuntimeException re ? re : new RuntimeException(e.getClass().getSimpleName());
                log.warn("메일 발송 실패(시도 {}/2): cat={} to={} reason={}",
                        attempt, category, masked, e.getClass().getSimpleName());
            }
        }
        record(category, refId, subject, masked, "failed",
                last == null ? "unknown" : last.getClass().getSimpleName());
    }

    /** 이력 기록(실패해도 호출 기능에 전파하지 않는다). */
    private void record(String category, String refId, String subject, String masked,
                        String status, String detail) {
        try {
            mailLogMapper.insert("ml-" + UUID.randomUUID().toString().replace("-", "").substring(0, 20),
                    category, refId, truncate(subject, 300), masked, status, truncate(detail, 300));
        } catch (RuntimeException e) {
            log.warn("메일 이력 기록 실패(무시): cat={} status={} reason={}", category, status, e.getClass().getSimpleName());
        }
    }

    /** 이메일 마스킹 — kim***@domain.com. 로그·이력 노출 안전 형식. */
    static String maskEmail(String email) {
        if (email == null || email.isBlank()) {
            return "***";
        }
        int at = email.indexOf('@');
        if (at <= 0) {
            return "***";
        }
        String local = email.substring(0, at);
        String domain = email.substring(at);
        if (local.length() <= 3) {
            return local.charAt(0) + "***" + domain;
        }
        return local.substring(0, 3) + "***" + domain;
    }

    private static String truncate(String s, int max) {
        if (s == null) {
            return null;
        }
        return s.length() <= max ? s : s.substring(0, max);
    }
}
