package cn.nianxx.thhotel.platform.message.service.impl;

import cn.nianxx.thhotel.platform.message.common.dto.ParsedEmlMediaItem;
import cn.nianxx.thhotel.platform.message.common.dto.ParsedEmlMessage;
import cn.nianxx.thhotel.platform.message.common.enums.SourceMessageMediaType;
import cn.nianxx.thhotel.platform.message.service.EmlMessageParseService;
import jakarta.mail.BodyPart;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import jakarta.mail.Multipart;
import jakarta.mail.Part;
import jakarta.mail.Session;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeUtility;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import org.springframework.stereotype.Service;

/**
 * EML 邮件解析服务实现。基于 Jakarta Mail 解析 MIME 层级，并把附件字节交给上层上传 OSS。
 */
@Service
public class EmlMessageParseServiceImpl implements EmlMessageParseService {

    /**
     * 解析单封 .eml 邮件，缺失 Message-ID 时由上层基于文件 hash 生成外部消息 ID。
     */
    @Override
    public ParsedEmlMessage parse(byte[] emlBytes, String originalFileName) {
        if (emlBytes == null || emlBytes.length == 0) {
            throw new EmlMessageParseException("上传的 .eml 文件为空。");
        }
        try {
            MimeMessage message = new MimeMessage(
                    Session.getInstance(new Properties()),
                    new ByteArrayInputStream(emlBytes));
            ParsedParts parts = new ParsedParts();
            collectPart(message, parts);
            String messageId = normalizeMessageId(message.getMessageID());
            return new ParsedEmlMessage(
                    messageId,
                    resolveConversationId(message, messageId),
                    resolveSender(message),
                    message.getSubject(),
                    toInstant(message.getSentDate()),
                    parts.textBody.toString(),
                    parts.htmlBody.toString(),
                    List.copyOf(parts.mediaItems));
        } catch (MessagingException | IOException exception) {
            throw new EmlMessageParseException("EML 邮件 MIME 结构解析失败。", exception);
        }
    }

    /**
     * 递归解析 MIME part，分别收集正文、HTML、内联图片和附件。
     */
    private void collectPart(Part part, ParsedParts parts) throws MessagingException, IOException {
        if (part.isMimeType("multipart/*")) {
            Multipart multipart = (Multipart) part.getContent();
            for (int index = 0; index < multipart.getCount(); index++) {
                BodyPart bodyPart = multipart.getBodyPart(index);
                collectPart(bodyPart, parts);
            }
            return;
        }

        String disposition = part.getDisposition();
        String fileName = decodeFileName(part.getFileName());
        String contentId = normalizeContentId(firstHeader(part, "Content-ID"));
        boolean attachment = Part.ATTACHMENT.equalsIgnoreCase(disposition);
        boolean inlineImage = contentId != null && contentType(part).startsWith("image/");
        if (attachment || inlineImage || shouldTreatAsAttachment(fileName, disposition)) {
            byte[] bytes = part.getInputStream().readAllBytes();
            String mediaType = inlineImage
                    ? SourceMessageMediaType.INLINE_IMAGE.code()
                    : SourceMessageMediaType.ATTACHMENT.code();
            parts.mediaItems.add(new ParsedEmlMediaItem(
                    mediaType,
                    fileName,
                    contentType(part),
                    (long) bytes.length,
                    contentId,
                    bytes));
            return;
        }

        if (part.isMimeType("text/plain")) {
            appendText(parts.textBody, readText(part));
            return;
        }
        if (part.isMimeType("text/html")) {
            appendText(parts.htmlBody, readText(part));
        }
    }

    /**
     * 判断带文件名但未明确 disposition 的 part 是否应视为附件。
     */
    private boolean shouldTreatAsAttachment(String fileName, String disposition) {
        if (fileName == null || fileName.isBlank()) {
            return false;
        }
        return disposition == null || Part.INLINE.equalsIgnoreCase(disposition);
    }

    /**
     * 读取文本 part 内容，Jakarta Mail 会根据 charset 解码字符串。
     */
    private String readText(Part part) throws MessagingException, IOException {
        Object content = part.getContent();
        if (content instanceof String text) {
            return text;
        }
        return new String(part.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
    }

    /**
     * 追加正文片段，多个 text/html part 之间用换行隔开。
     */
    private void appendText(StringBuilder builder, String value) {
        if (value == null || value.isBlank()) {
            return;
        }
        if (!builder.isEmpty()) {
            builder.append('\n');
        }
        builder.append(value);
    }

    /**
     * 解析发件人，优先返回邮箱地址，缺失时返回 Jakarta Mail 的原始展示值。
     */
    private String resolveSender(MimeMessage message) throws MessagingException {
        jakarta.mail.Address[] from = message.getFrom();
        if (from == null || from.length == 0) {
            return null;
        }
        if (from[0] instanceof InternetAddress address && address.getAddress() != null) {
            return address.getAddress();
        }
        return from[0].toString();
    }

    /**
     * 解析会话 ID，优先使用 References / In-Reply-To 的第一项，其次使用 Thread-Index，最后回退 Message-ID。
     */
    private String resolveConversationId(MimeMessage message, String messageId) throws MessagingException {
        String references = message.getHeader("References", null);
        String firstReference = firstMessageId(references);
        if (firstReference != null) {
            return firstReference;
        }
        String inReplyTo = firstMessageId(message.getHeader("In-Reply-To", null));
        if (inReplyTo != null) {
            return inReplyTo;
        }
        String threadIndex = trimToNull(message.getHeader("Thread-Index", null));
        return threadIndex == null ? messageId : threadIndex;
    }

    /**
     * 从 References 类头字段中提取第一个 message id。
     */
    private String firstMessageId(String header) {
        if (header == null || header.isBlank()) {
            return null;
        }
        String[] parts = header.trim().split("\\s+");
        return normalizeMessageId(parts.length == 0 ? header : parts[0]);
    }

    /**
     * 规范化 Message-ID 或 Content-ID，去掉尖括号和 cid: 前缀。
     */
    private String normalizeMessageId(String value) {
        if (value == null) {
            return null;
        }
        String normalized = value.trim();
        if (normalized.toLowerCase(Locale.ROOT).startsWith("cid:")) {
            normalized = normalized.substring(4);
        }
        if (normalized.startsWith("<") && normalized.endsWith(">") && normalized.length() > 2) {
            normalized = normalized.substring(1, normalized.length() - 1);
        }
        return normalized.isBlank() ? null : normalized;
    }

    /**
     * 规范化 Content-ID。
     */
    private String normalizeContentId(String value) {
        return normalizeMessageId(value);
    }

    /**
     * 空白字符串转 null。
     */
    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }

    /**
     * 读取 MIME part 的首个头字段值，缺失时返回 null。
     */
    private String firstHeader(Part part, String headerName) throws MessagingException {
        String[] values = part.getHeader(headerName);
        return values == null || values.length == 0 ? null : values[0];
    }

    /**
     * 解码 MIME 文件名，解析失败时回退原始文件名。
     */
    private String decodeFileName(String fileName) {
        if (fileName == null) {
            return null;
        }
        try {
            return MimeUtility.decodeText(fileName);
        } catch (Exception exception) {
            return fileName;
        }
    }

    /**
     * 读取 part 的基础 MIME 类型，去掉 charset/name 等参数。
     */
    private String contentType(Part part) throws MessagingException {
        String contentType = part.getContentType();
        if (contentType == null) {
            return "application/octet-stream";
        }
        int semicolonIndex = contentType.indexOf(';');
        String baseType = semicolonIndex >= 0 ? contentType.substring(0, semicolonIndex) : contentType;
        return baseType.trim().toLowerCase(Locale.ROOT);
    }

    /**
     * 将 java.util.Date 转为 Instant，缺失时返回 null。
     */
    private Instant toInstant(Date date) {
        return date == null ? null : date.toInstant();
    }

    /**
     * 递归解析过程中的可变收集器。
     */
    private static final class ParsedParts {
        private final StringBuilder textBody = new StringBuilder();
        private final StringBuilder htmlBody = new StringBuilder();
        private final List<ParsedEmlMediaItem> mediaItems = new ArrayList<>();
    }
}
