package cn.nianxx.thhotel.platform.message.repository;

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.SourceMessageOriginalAccessAuditDraft;
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageOriginalContent;
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageOriginalMediaItem;
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessagePayloadDuplicateDraft;
import cn.nianxx.thhotel.platform.message.common.enums.SourceMessageBodyContentType;
import cn.nianxx.thhotel.platform.message.common.enums.SourceMessageCaptureStatus;
import cn.nianxx.thhotel.platform.message.common.request.CaptureSourceMessageMedia;
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageQueryRequest;
import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
import cn.nianxx.thhotel.platform.message.domain.SourceMessageBodyEntity;
import cn.nianxx.thhotel.platform.message.domain.SourceMessageInboxEntity;
import cn.nianxx.thhotel.platform.message.domain.SourceMessageMediaEntity;
import cn.nianxx.thhotel.platform.message.domain.SourceMessageOriginalAccessAuditEntity;
import cn.nianxx.thhotel.platform.message.domain.SourceMessagePayloadDuplicateEntity;
import cn.nianxx.thhotel.platform.message.domain.SourceMessagePayloadEntity;
import cn.nianxx.thhotel.platform.message.mapper.SourceMessageBodyMapper;
import cn.nianxx.thhotel.platform.message.mapper.SourceMessageInboxMapper;
import cn.nianxx.thhotel.platform.message.mapper.SourceMessageMediaMapper;
import cn.nianxx.thhotel.platform.message.mapper.SourceMessageOriginalAccessAuditMapper;
import cn.nianxx.thhotel.platform.message.mapper.SourceMessagePayloadDuplicateMapper;
import cn.nianxx.thhotel.platform.message.mapper.SourceMessagePayloadMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.util.HexFormat;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.springframework.stereotype.Repository;

/**
 * SourceMessage Inbox 的 MyBatis-Plus 持久化实现。数据库 Entity 和 Mapper 只停留在本层。
 */
@Repository
public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRepository {

    private final SourceMessageInboxMapper inboxMapper;
    private final SourceMessagePayloadMapper payloadMapper;
    private final SourceMessageBodyMapper bodyMapper;
    private final SourceMessageMediaMapper mediaMapper;
    private final SourceMessageOriginalAccessAuditMapper originalAccessAuditMapper;
    private final SourceMessagePayloadDuplicateMapper payloadDuplicateMapper;

    /**
     * 注入本模块 Mapper，数据库访问细节只保留在 Repository 实现内部。
     */
    public MybatisSourceMessageInboxRepository(
            SourceMessageInboxMapper inboxMapper,
            SourceMessagePayloadMapper payloadMapper,
            SourceMessageBodyMapper bodyMapper,
            SourceMessageMediaMapper mediaMapper,
            SourceMessageOriginalAccessAuditMapper originalAccessAuditMapper,
            SourceMessagePayloadDuplicateMapper payloadDuplicateMapper) {
        this.inboxMapper = inboxMapper;
        this.payloadMapper = payloadMapper;
        this.bodyMapper = bodyMapper;
        this.mediaMapper = mediaMapper;
        this.originalAccessAuditMapper = originalAccessAuditMapper;
        this.payloadDuplicateMapper = payloadDuplicateMapper;
    }

    /**
     * 根据内部主键读取 Inbox 快照，避免 Service 直接接触数据库 Entity。
     */
    @Override
    public Optional<SourceMessageInboxSnapshot> findById(Long id) {
        if (id == null) {
            return Optional.empty();
        }
        return Optional.ofNullable(inboxMapper.selectById(id)).map(this::toSnapshot);
    }

    /**
     * 批量读取 Inbox 安全快照，只读取 Inbox 索引表字段，不访问正文、媒体或 payload 表。
     */
    @Override
    public List<SourceMessageInboxSnapshot> findByIds(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return List.of();
        }
        List<Long> safeIds = ids.stream().filter(Objects::nonNull).distinct().toList();
        if (safeIds.isEmpty()) {
            return List.of();
        }
        return inboxMapper.selectList(Wrappers.<SourceMessageInboxEntity>lambdaQuery()
                        .in(SourceMessageInboxEntity::getId, safeIds))
                .stream()
                .map(this::toSnapshot)
                .toList();
    }

    /**
     * 查询同一外部会话下的全部 SourceMessage 安全快照，按接收时间正序返回。
     */
    @Override
    public List<SourceMessageInboxSnapshot> findByExternalConversationId(String hotelId, String externalConversationId) {
        if (!hasText(hotelId) || !hasText(externalConversationId)) {
            return List.of();
        }
        return inboxMapper.selectList(Wrappers.<SourceMessageInboxEntity>lambdaQuery()
                        .eq(SourceMessageInboxEntity::getHotelId, trim(hotelId))
                        .eq(SourceMessageInboxEntity::getExternalConversationId, trim(externalConversationId))
                        .orderByAsc(SourceMessageInboxEntity::getReceivedAt)
                        .orderByAsc(SourceMessageInboxEntity::getId))
                .stream()
                .map(this::toSnapshot)
                .toList();
    }

    /**
     * 查询指定来源和渠道下的同一外部会话，避免不同接入方会话 ID 偶然相同导致串数据。
     */
    @Override
    public List<SourceMessageInboxSnapshot> findByExternalConversationId(
            String hotelId,
            String provider,
            String channel,
            String externalConversationId) {
        if (!hasText(hotelId) || !hasText(provider) || !hasText(channel) || !hasText(externalConversationId)) {
            return List.of();
        }
        return inboxMapper.selectList(Wrappers.<SourceMessageInboxEntity>lambdaQuery()
                        .eq(SourceMessageInboxEntity::getHotelId, trim(hotelId))
                        .eq(SourceMessageInboxEntity::getProvider, trim(provider))
                        .eq(SourceMessageInboxEntity::getChannel, trim(channel))
                        .eq(SourceMessageInboxEntity::getExternalConversationId, trim(externalConversationId))
                        .orderByAsc(SourceMessageInboxEntity::getReceivedAt)
                        .orderByAsc(SourceMessageInboxEntity::getId))
                .stream()
                .map(this::toSnapshot)
                .toList();
    }

    /**
     * 根据 SourceMessage 幂等键读取已有记录，用于重复投递判断。
     */
    @Override
    public Optional<SourceMessageInboxSnapshot> findByIdempotencyKey(
            String hotelId,
            String provider,
            String channel,
            String externalMessageId) {
        SourceMessageInboxEntity entity = inboxMapper.selectOne(Wrappers.<SourceMessageInboxEntity>lambdaQuery()
                .eq(SourceMessageInboxEntity::getHotelId, hotelId)
                .eq(SourceMessageInboxEntity::getProvider, provider)
                .eq(SourceMessageInboxEntity::getChannel, channel)
                .eq(SourceMessageInboxEntity::getExternalMessageId, externalMessageId));
        return Optional.ofNullable(entity).map(this::toSnapshot);
    }

    /**
     * 根据酒店和外部邮件 ID 查询所有候选 Inbox 记录，调用方负责处理零条或多条的业务语义。
     */
    @Override
    public List<SourceMessageInboxSnapshot> findByExternalMessageId(String hotelId, String externalMessageId) {
        if (!hasText(hotelId) || !hasText(externalMessageId)) {
            return List.of();
        }
        return inboxMapper.selectList(Wrappers.<SourceMessageInboxEntity>lambdaQuery()
                        .eq(SourceMessageInboxEntity::getHotelId, trim(hotelId))
                        .eq(SourceMessageInboxEntity::getExternalMessageId, trim(externalMessageId))
                        .orderByDesc(SourceMessageInboxEntity::getReceivedAt)
                        .orderByDesc(SourceMessageInboxEntity::getId))
                .stream()
                .map(this::toSnapshot)
                .toList();
    }

    /**
     * 执行 SourceMessage 安全摘要分页查询，只读取列表和详情需要的索引字段。
     */
    @Override
    public SourceMessagePageResult<SourceMessageInboxSnapshot> query(
            SourceMessageQueryRequest request,
            int pageNum,
            int pageSize) {
        Page<SourceMessageInboxEntity> page = inboxMapper.selectPage(Page.of(pageNum, pageSize),
                Wrappers.<SourceMessageInboxEntity>lambdaQuery()
                        .eq(hasText(request.hotelId()), SourceMessageInboxEntity::getHotelId, trim(request.hotelId()))
                        .eq(hasText(request.externalMessageId()),
                                SourceMessageInboxEntity::getExternalMessageId,
                                trim(request.externalMessageId()))
                        .eq(hasText(request.externalConversationId()),
                                SourceMessageInboxEntity::getExternalConversationId,
                                trim(request.externalConversationId()))
                        .eq(hasText(request.captureStatus()),
                                SourceMessageInboxEntity::getCaptureStatus,
                                trim(request.captureStatus()))
                        .orderByDesc(SourceMessageInboxEntity::getReceivedAt));
        List<SourceMessageInboxSnapshot> items = page.getRecords().stream().map(this::toSnapshot).toList();
        return new SourceMessagePageResult<>(items, page.getTotal(), pageNum, pageSize);
    }

    /**
     * 按安全摘要关键词查询 SourceMessage ID。这里只查询列表可展示字段，不触碰正文、HTML、附件 URL 或 payload。
     */
    @Override
    public List<Long> findIdsBySafeKeyword(String hotelId, String keyword, int limit) {
        if (!hasText(keyword) || limit < 1) {
            return List.of();
        }
        String trimmedKeyword = trim(keyword);
        Page<SourceMessageInboxEntity> page = inboxMapper.selectPage(Page.of(1, limit),
                Wrappers.<SourceMessageInboxEntity>lambdaQuery()
                        .eq(hasText(hotelId), SourceMessageInboxEntity::getHotelId, trim(hotelId))
                        .and(wrapper -> wrapper
                                .like(SourceMessageInboxEntity::getExternalMessageId, trimmedKeyword)
                                .or()
                                .like(SourceMessageInboxEntity::getExternalConversationId, trimmedKeyword)
                                .or()
                                .like(SourceMessageInboxEntity::getSenderSummary, trimmedKeyword)
                                .or()
                                .like(SourceMessageInboxEntity::getSubject, trimmedKeyword)
                                .or()
                                .like(SourceMessageInboxEntity::getSafeSnippet, trimmedKeyword))
                        .orderByDesc(SourceMessageInboxEntity::getReceivedAt));
        return page.getRecords().stream()
                .map(SourceMessageInboxEntity::getId)
                .toList();
    }

    /**
     * 批量统计会话消息数。该方法只访问 Inbox 索引表，不读取正文或媒体 URL。
     */
    @Override
    public Map<String, Long> countByExternalConversationIds(String hotelId, List<String> externalConversationIds) {
        if (!hasText(hotelId) || externalConversationIds == null || externalConversationIds.isEmpty()) {
            return Map.of();
        }
        List<String> safeConversationIds = externalConversationIds.stream()
                .filter(this::hasText)
                .map(this::trim)
                .distinct()
                .toList();
        if (safeConversationIds.isEmpty()) {
            return Map.of();
        }
        Map<String, Long> counts = new LinkedHashMap<>();
        inboxMapper.selectList(Wrappers.<SourceMessageInboxEntity>lambdaQuery()
                        .eq(SourceMessageInboxEntity::getHotelId, trim(hotelId))
                        .in(SourceMessageInboxEntity::getExternalConversationId, safeConversationIds))
                .forEach(entity -> counts.merge(entity.getExternalConversationId(), 1L, Long::sum));
        return counts;
    }

    /**
     * 插入 Inbox 主记录及原始 payload；RECEIVED 状态额外保存正文和媒体引用。
     */
    @Override
    public Long insert(SourceMessageInboxDraft draft) {
        SourceMessageInboxEntity inbox = toInboxEntity(draft);
        inboxMapper.insert(inbox);
        insertPayload(draft, inbox.getId());
        if (SourceMessageCaptureStatus.RECEIVED.code().equals(draft.captureStatus())) {
            insertBody(draft, inbox.getId());
            insertMedia(draft.mediaItems(), inbox.getId(), draft.capturedAt());
        }
        return inbox.getId();
    }

    /**
     * 记录重复投递 payload 差异，不覆盖第一次保存的原始 payload。
     */
    @Override
    public void markDuplicatePayloadChanged(Long id, String safeErrorSummary, LocalDateTime updatedAt) {
        SourceMessageInboxEntity entity = new SourceMessageInboxEntity();
        entity.setId(id);
        entity.setDuplicatePayloadChanged(Boolean.TRUE);
        entity.setSafeErrorSummary(safeErrorSummary);
        entity.setUpdatedAt(updatedAt);
        inboxMapper.updateById(entity);
    }

    /**
     * 追加保存重复投递 payload 差异明细，不覆盖首次保存的原始 payload。
     */
    @Override
    public void insertDuplicatePayload(SourceMessagePayloadDuplicateDraft draft) {
        SourceMessagePayloadDuplicateEntity entity = new SourceMessagePayloadDuplicateEntity();
        entity.setInboxId(draft.inboxId());
        entity.setOriginalPayloadSha256(draft.originalPayloadSha256());
        entity.setDuplicatePayloadSha256(draft.duplicatePayloadSha256());
        entity.setPayloadJson(draft.payloadJson());
        entity.setProviderFrameId(draft.providerFrameId());
        entity.setProviderSessionId(draft.providerSessionId());
        entity.setSchemaVersion(draft.schemaVersion());
        entity.setReceivedAt(draft.receivedAt());
        entity.setCreatedAt(draft.receivedAt());
        payloadDuplicateMapper.insert(entity);
    }

    /**
     * 读取 SourceMessage 原文内容和媒体 URL。该方法不供普通摘要查询调用。
     */
    @Override
    public Optional<SourceMessageOriginalContent> findOriginalContent(Long id) {
        if (id == null || inboxMapper.selectById(id) == null) {
            return Optional.empty();
        }
        SourceMessageBodyEntity body = bodyMapper.selectOne(Wrappers.<SourceMessageBodyEntity>lambdaQuery()
                .eq(SourceMessageBodyEntity::getInboxId, id));
        List<SourceMessageOriginalMediaItem> mediaItems = mediaMapper.selectList(Wrappers.<SourceMessageMediaEntity>lambdaQuery()
                        .eq(SourceMessageMediaEntity::getInboxId, id)
                        .orderByAsc(SourceMessageMediaEntity::getId))
                .stream()
                .map(this::toOriginalMediaItem)
                .toList();
        return Optional.of(new SourceMessageOriginalContent(
                id,
                body == null ? null : body.getTextBody(),
                body == null ? null : body.getHtmlBody(),
                mediaItems
        ));
    }

    /**
     * 写入原文读取审计，Repository 负责把服务层审计草稿转换为数据库实体。
     */
    @Override
    public void insertOriginalAccessAudit(SourceMessageOriginalAccessAuditDraft draft) {
        SourceMessageOriginalAccessAuditEntity entity = new SourceMessageOriginalAccessAuditEntity();
        entity.setInboxId(draft.inboxId());
        entity.setActorId(draft.actorId());
        entity.setAccessScene(draft.accessScene());
        entity.setAccessResult(draft.accessResult());
        entity.setAccessedAt(draft.accessedAt());
        entity.setCreatedAt(draft.accessedAt());
        originalAccessAuditMapper.insert(entity);
    }

    /**
     * 将服务层入库草稿转换为 Inbox 数据库实体。
     */
    private SourceMessageInboxEntity toInboxEntity(SourceMessageInboxDraft draft) {
        SourceMessageInboxEntity inbox = new SourceMessageInboxEntity();
        inbox.setHotelId(draft.hotelId());
        inbox.setProvider(draft.provider());
        inbox.setChannel(draft.channel());
        inbox.setExternalMessageId(draft.externalMessageId());
        inbox.setExternalConversationId(draft.externalConversationId());
        inbox.setProviderFrameId(draft.providerFrameId());
        inbox.setProviderSessionId(draft.providerSessionId());
        inbox.setPayloadSha256(draft.payloadSha256());
        inbox.setCaptureStatus(draft.captureStatus());
        inbox.setDuplicatePayloadChanged(draft.duplicatePayloadChanged());
        inbox.setSafeErrorSummary(draft.safeErrorSummary());
        inbox.setReceivedAt(draft.receivedAt());
        inbox.setSourceSentAt(draft.sourceSentAt());
        inbox.setSenderSummary(draft.senderSummary());
        inbox.setSubject(draft.subject());
        inbox.setSafeSnippet(draft.safeSnippet());
        inbox.setCreatedAt(draft.capturedAt());
        inbox.setUpdatedAt(draft.capturedAt());
        return inbox;
    }

    /**
     * 保存原始 payload 记录，普通查询接口不会读取该表内容。
     */
    private void insertPayload(SourceMessageInboxDraft draft, Long inboxId) {
        SourceMessagePayloadEntity payload = new SourceMessagePayloadEntity();
        payload.setInboxId(inboxId);
        payload.setPayloadJson(draft.payloadJson());
        payload.setPayloadSha256(draft.payloadSha256());
        payload.setSchemaVersion(draft.schemaVersion());
        payload.setCreatedAt(draft.capturedAt());
        payloadMapper.insert(payload);
    }

    /**
     * 保存正文记录，为后续受控原文读取保留追溯能力。
     */
    private void insertBody(SourceMessageInboxDraft draft, Long inboxId) {
        SourceMessageBodyEntity body = new SourceMessageBodyEntity();
        body.setInboxId(inboxId);
        body.setContentType(SourceMessageBodyContentType.MIXED.code());
        body.setTextBody(draft.textBody());
        body.setHtmlBody(draft.htmlBody());
        body.setBodySha256(sha256(draft.textBody() + "\n" + draft.htmlBody()));
        body.setCreatedAt(draft.capturedAt());
        bodyMapper.insert(body);
    }

    /**
     * 保存媒体引用记录，只登记外部 URL，不在当前服务下载附件。
     */
    private void insertMedia(List<CaptureSourceMessageMedia> mediaItems, Long inboxId, LocalDateTime now) {
        if (mediaItems == null || mediaItems.isEmpty()) {
            return;
        }
        for (CaptureSourceMessageMedia item : mediaItems) {
            SourceMessageMediaEntity media = new SourceMessageMediaEntity();
            media.setInboxId(inboxId);
            media.setMediaType(item.mediaType());
            media.setFileName(item.fileName());
            media.setContentType(item.contentType());
            media.setSizeBytes(item.sizeBytes());
            media.setExternalUrl(item.externalUrl());
            media.setExternalMediaId(item.externalMediaId());
            media.setCreatedAt(now);
            mediaMapper.insert(media);
        }
    }

    /**
     * 判断查询条件是否有有效文本，避免空白字符串参与数据库过滤。
     */
    private boolean hasText(String value) {
        return value != null && !value.trim().isEmpty();
    }

    /**
     * 统一清理查询参数首尾空白，避免 Repository 调用 Mapper 时重复表达 trim 逻辑。
     */
    private String trim(String value) {
        return value == null ? null : value.trim();
    }

    /**
     * 将数据库实体转换为服务层快照，隐藏 Entity 和 Mapper 细节。
     */
    private SourceMessageInboxSnapshot toSnapshot(SourceMessageInboxEntity entity) {
        return new SourceMessageInboxSnapshot(
                entity.getId(),
                entity.getHotelId(),
                entity.getProvider(),
                entity.getChannel(),
                entity.getExternalMessageId(),
                entity.getExternalConversationId(),
                entity.getPayloadSha256(),
                entity.getCaptureStatus(),
                Boolean.TRUE.equals(entity.getDuplicatePayloadChanged()),
                entity.getReceivedAt(),
                entity.getSourceSentAt(),
                entity.getSenderSummary(),
                entity.getSubject(),
                entity.getSafeSnippet()
        );
    }

    /**
     * 将媒体 Entity 转为原文读取内部 DTO，避免 Entity 泄漏到 Service。
     */
    private SourceMessageOriginalMediaItem toOriginalMediaItem(SourceMessageMediaEntity entity) {
        return new SourceMessageOriginalMediaItem(
                entity.getMediaType(),
                entity.getFileName(),
                entity.getContentType(),
                entity.getSizeBytes(),
                entity.getExternalUrl(),
                entity.getExternalMediaId()
        );
    }

    /**
     * 计算正文内容 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);
        }
    }
}
