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

import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageInboxDraft;
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageInboxSnapshot;
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessagePayloadDuplicateDraft;
import cn.nianxx.thhotel.platform.message.common.enums.SourceMessageCaptureFailureReason;
import cn.nianxx.thhotel.platform.message.common.enums.SourceMessageCaptureStatus;
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageCommand;
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageMedia;
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageCaptureResult;
import cn.nianxx.thhotel.platform.message.repository.SourceMessageInboxRepository;
import cn.nianxx.thhotel.platform.message.service.SourceMessageCaptureService;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HexFormat;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 来源消息捕获服务实现。负责幂等、状态、安全摘要和事务边界，不依赖数据库 Mapper。
 */
@Service
public class SourceMessageCaptureServiceImpl implements SourceMessageCaptureService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SourceMessageCaptureServiceImpl.class);

    private static final String DUPLICATE_PAYLOAD_CHANGED_SUMMARY = "重复投递 payload 与原始 payload 不一致，已保留原始载荷。";

    private final SourceMessageInboxRepository inboxRepository;
    private final SourceMessageSafetySanitizer safetySanitizer;

    /**
     * 注入 SourceMessage 持久化边界和安全摘要工具，Service 实现不直接依赖 Mapper。
     */
    public SourceMessageCaptureServiceImpl(
            SourceMessageInboxRepository inboxRepository,
            SourceMessageSafetySanitizer safetySanitizer) {
        this.inboxRepository = inboxRepository;
        this.safetySanitizer = safetySanitizer;
    }

    /**
     * 捕获 AgentBus 等外部入口映射后的稳定消息命令，只落 SourceMessage Inbox，不触发业务任务。
     */
    @Override
    @Transactional
    public SourceMessageCaptureResult capture(CaptureSourceMessageCommand command) {
        String hotelId = requireText(command.hotelId(), "hotelId");
        String provider = requireText(command.provider(), "provider");
        String channel = requireText(command.channel(), "channel");
        String externalMessageId = trimToNull(command.externalMessageId());
        String payloadJson = command.payloadJson() == null ? "" : command.payloadJson();
        String payloadSha256 = sha256(payloadJson);
        if (externalMessageId == null) {
            return captureFailed(
                    command,
                    hotelId,
                    provider,
                    channel,
                    null,
                    payloadSha256,
                    SourceMessageCaptureFailureReason.MISSING_EXTERNAL_MESSAGE_ID);
        }

        SourceMessageInboxSnapshot existing = selectExisting(hotelId, provider, channel, externalMessageId);
        if (existing != null) {
            return existingResult(existing, payloadSha256, command);
        }

        SourceMessageCaptureFailureReason mediaValidationError = validateMediaItems(command.mediaItems());
        if (mediaValidationError != null) {
            return captureFailed(
                    command,
                    hotelId,
                    provider,
                    channel,
                    externalMessageId,
                    payloadSha256,
                    mediaValidationError);
        }

        SourceMessageInboxDraft draft = buildDraft(
                command,
                hotelId,
                provider,
                channel,
                externalMessageId,
                payloadSha256,
                SourceMessageCaptureStatus.RECEIVED,
                null);
        try {
            Long inboxId = inboxRepository.insert(draft);
            return new SourceMessageCaptureResult(inboxId, true, false, SourceMessageCaptureStatus.RECEIVED.code());
        } catch (DuplicateKeyException exception) {
            // 并发重复投递时，数据库唯一键是最终幂等防线；命中后回查已有 Inbox。
            SourceMessageInboxSnapshot concurrentlyCreated = selectExisting(hotelId, provider, channel, externalMessageId);
            if (concurrentlyCreated != null) {
                return existingResult(concurrentlyCreated, payloadSha256, command);
            }
            throw exception;
        }
    }

    /**
     * 按失败原因保存 FAILED 状态 Inbox，错误摘要必须是安全摘要而不是原始正文。
     */
    private SourceMessageCaptureResult captureFailed(
            CaptureSourceMessageCommand command,
            String hotelId,
            String provider,
            String channel,
            String externalMessageId,
            String payloadSha256,
            SourceMessageCaptureFailureReason failureReason) {
        SourceMessageInboxDraft draft = buildDraft(
                command,
                hotelId,
                provider,
                channel,
                externalMessageId,
                payloadSha256,
                SourceMessageCaptureStatus.FAILED,
                failureReason.safeSummary());
        Long inboxId = inboxRepository.insert(draft);
        return new SourceMessageCaptureResult(inboxId, true, false, SourceMessageCaptureStatus.FAILED.code());
    }

    /**
     * 按 SourceMessage 幂等键回查已有记录，用于重复投递和并发唯一键冲突处理。
     */
    private SourceMessageInboxSnapshot selectExisting(
            String hotelId,
            String provider,
            String channel,
            String externalMessageId) {
        return inboxRepository.findByIdempotencyKey(hotelId, provider, channel, externalMessageId).orElse(null);
    }

    /**
     * 将已存在的 Inbox 转换为捕获结果，并在 payload 变化时只记录差异标记。
     */
    private SourceMessageCaptureResult existingResult(
            SourceMessageInboxSnapshot existing,
            String payloadSha256,
            CaptureSourceMessageCommand command) {
        boolean payloadChanged = !payloadSha256.equals(existing.payloadSha256());
        if (payloadChanged) {
            LocalDateTime now = nowUtc();
            if (!existing.duplicatePayloadChanged()) {
                inboxRepository.markDuplicatePayloadChanged(existing.id(), DUPLICATE_PAYLOAD_CHANGED_SUMMARY, now);
            }
            recordDuplicatePayload(existing, payloadSha256, command, now);
        }
        return new SourceMessageCaptureResult(existing.id(), false, payloadChanged, existing.captureStatus());
    }

    /**
     * 记录重复投递 payload 明细用于排查；诊断写入失败不影响原有幂等返回。
     */
    private void recordDuplicatePayload(
            SourceMessageInboxSnapshot existing,
            String duplicatePayloadSha256,
            CaptureSourceMessageCommand command,
            LocalDateTime receivedAt) {
        try {
            inboxRepository.insertDuplicatePayload(new SourceMessagePayloadDuplicateDraft(
                    existing.id(),
                    existing.payloadSha256(),
                    duplicatePayloadSha256,
                    command.payloadJson() == null ? "" : command.payloadJson(),
                    trimToNull(command.providerFrameId()),
                    trimToNull(command.providerSessionId()),
                    trimToNull(command.schemaVersion()),
                    receivedAt
            ));
        } catch (RuntimeException exception) {
            LOGGER.warn(
                    "重复投递 payload 诊断写入失败，inboxId={}, originalPayloadSha256={}, duplicatePayloadSha256={}, exceptionType={}",
                    existing.id(),
                    existing.payloadSha256(),
                    duplicatePayloadSha256,
                    exception.getClass().getName());
        }
    }

    /**
     * 校验媒体引用最小必填字段，避免保存无法追溯的附件或内嵌媒体记录。
     */
    private SourceMessageCaptureFailureReason validateMediaItems(List<CaptureSourceMessageMedia> mediaItems) {
        if (mediaItems == null || mediaItems.isEmpty()) {
            return null;
        }
        for (CaptureSourceMessageMedia item : mediaItems) {
            if (item == null) {
                return SourceMessageCaptureFailureReason.INVALID_MEDIA_ITEM;
            }
            if (trimToNull(item.mediaType()) == null) {
                return SourceMessageCaptureFailureReason.MISSING_MEDIA_TYPE;
            }
            if (trimToNull(item.externalUrl()) == null) {
                return SourceMessageCaptureFailureReason.MISSING_MEDIA_EXTERNAL_URL;
            }
        }
        return null;
    }

    /**
     * 构建入库草稿，统一生成安全主题、发件人展示值和正文摘要。
     */
    private SourceMessageInboxDraft buildDraft(
            CaptureSourceMessageCommand command,
            String hotelId,
            String provider,
            String channel,
            String externalMessageId,
            String payloadSha256,
            SourceMessageCaptureStatus captureStatus,
            String safeErrorSummary) {
        LocalDateTime capturedAt = nowUtc();
        LocalDateTime sourceReceivedAt = toLocalDateTime(command.sourceReceivedAt());
        LocalDateTime receivedAt = sourceReceivedAt == null ? capturedAt : sourceReceivedAt;
        String textBody = command.textBody() == null ? "" : command.textBody();
        String htmlBody = command.htmlBody() == null ? "" : command.htmlBody();
        // 邮件主题也可能携带联系方式或带 token 的 URL，普通查询只保存可展示摘要。
        String safeSubject = safetySanitizer.safeSnippet(command.subject());
        String safeSnippet = SourceMessageCaptureStatus.FAILED == captureStatus
                ? safeErrorSummary
                : safetySanitizer.safeSnippet(textBody);
        return new SourceMessageInboxDraft(
                hotelId,
                provider,
                channel,
                externalMessageId,
                trimToNull(command.externalConversationId()),
                trimToNull(command.providerFrameId()),
                trimToNull(command.providerSessionId()),
                payloadSha256,
                captureStatus.code(),
                false,
                safeErrorSummary,
                receivedAt,
                capturedAt,
                toLocalDateTime(command.sourceSentAt()),
                safetySanitizer.senderSummary(command.senderIdentifier()),
                safeSubject,
                safeSnippet,
                command.payloadJson() == null ? "" : command.payloadJson(),
                trimToNull(command.schemaVersion()),
                textBody,
                htmlBody,
                command.mediaItems()
        );
    }

    /**
     * 计算原始 payload 的 SHA-256，用于重复投递内容变化判断。
     */
    private String sha256(String value) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] bytes = digest.digest(value.getBytes(StandardCharsets.UTF_8));
            return HexFormat.of().formatHex(bytes);
        } catch (NoSuchAlgorithmException exception) {
            throw new IllegalStateException("当前 Java 运行时不支持 SHA-256", exception);
        }
    }

    /**
     * 校验必填文本字段，返回清理后的值，防止空白字符串进入幂等键。
     */
    private String requireText(String value, String fieldName) {
        String trimmed = trimToNull(value);
        if (trimmed == null) {
            throw new IllegalArgumentException(fieldName + " must not be blank");
        }
        return trimmed;
    }

    /**
     * 将空白字符串统一转换为 null，保证可选字段入库语义一致。
     */
    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }

    /**
     * 将来源系统时间转换为 UTC 本地时间字段，缺失时保持为空。
     */
    private LocalDateTime toLocalDateTime(Instant instant) {
        if (instant == null) {
            return null;
        }
        return LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
    }

    /**
     * 生成当前 UTC 时间，保证数据库业务时间不混入服务器本地时区。
     */
    private LocalDateTime nowUtc() {
        return LocalDateTime.now(ZoneOffset.UTC);
    }
}
