package com.sino.mci.channel.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sino.mci.channel.messaging.SessionArchivePullCommand;
import com.sino.mci.channel.wecom.archive.FinanceSdkClient;
import com.sino.mci.channel.wecom.archive.model.FinanceMessage;
import com.sino.mci.inbound.entity.MessageEvent;
import com.sino.mci.inbound.repository.MessageEventMapper;
import com.sino.mci.inbound.service.InboundMessageService;
import com.sino.mci.message.entity.MessageMedia;
import com.sino.mci.message.repository.MessageMediaMapper;
import com.sino.mci.shared.util.JsonUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * 企微会话存档消息拉取与落库服务。
 *
 * <p>消费 RabbitMQ 命令，按渠道主体维度使用 Redis 缓存 seq 游标，调用 Finance SDK 拉取、解密，
 * 并将消息写入 {@code mci_message_event}，媒体文件写入 {@code mci_message_media}。</p>
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SessionArchivePullService {

    private static final String SEQ_KEY_PREFIX = "wecom:session_archive:seq:";
    private static final int DEFAULT_LIMIT = 100;
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private final FinanceSdkClient financeSdkClient;
    private final StringRedisTemplate redisTemplate;
    private final MessageEventMapper messageEventMapper;
    private final MessageMediaMapper messageMediaMapper;
    private final InboundMessageService inboundMessageService;

    /**
     * 消费会话存档拉取命令。
     */
    @RabbitListener(queues = "${sino.channel.wecom.session-archive.queue:mci.wecom.session_archive.pull}")
    @Transactional
    public void onPullCommand(SessionArchivePullCommand command) {
        log.info("[SessionArchive] 开始拉取会话存档 subjectId={}, corpId={}",
                command.getChannelSubjectId(), command.getCorpId());

        if (!StringUtils.hasText(command.getCorpId()) || !StringUtils.hasText(command.getSecret())) {
            log.warn("[SessionArchive] 缺少 corpId 或 secret，跳过拉取");
            return;
        }

        if (!financeSdkClient.init(command.getCorpId(), command.getSecret())) {
            log.warn("[SessionArchive] Finance SDK 初始化失败 corpId={}", command.getCorpId());
            return;
        }

        long currentSeq = readCurrentSeq(command.getChannelSubjectId());
        List<FinanceMessage> messages = financeSdkClient.getChatData(currentSeq, DEFAULT_LIMIT);
        if (messages == null || messages.isEmpty()) {
            log.info("[SessionArchive] 无新消息 subjectId={}, seq={}", command.getChannelSubjectId(), currentSeq);
            return;
        }

        long maxSeq = currentSeq;
        for (FinanceMessage msg : messages) {
            String decryptedJson = decryptMessage(command, msg);
            if (!StringUtils.hasText(decryptedJson)) {
                log.warn("[SessionArchive] 解密失败 seq={}, msgId={}", msg.getSeq(), msg.getMsgId());
                continue;
            }
            saveMessageEvent(command, msg, decryptedJson);
            saveMediaIfNeeded(command, msg, decryptedJson);
            maxSeq = Math.max(maxSeq, msg.getSeq() + 1);
        }

        updateSeq(command.getChannelSubjectId(), maxSeq);
        log.info("[SessionArchive] 拉取完成 subjectId={}, messages={}, nextSeq={}",
                command.getChannelSubjectId(), messages.size(), maxSeq);
    }

    private long readCurrentSeq(Long subjectId) {
        String key = seqKey(subjectId);
        String value = redisTemplate.opsForValue().get(key);
        if (!StringUtils.hasText(value)) {
            return 0L;
        }
        try {
            return Long.parseLong(value);
        } catch (NumberFormatException e) {
            log.warn("[SessionArchive] seq 缓存格式异常，重置为 0 key={}", key);
            return 0L;
        }
    }

    private void updateSeq(Long subjectId, long seq) {
        redisTemplate.opsForValue().set(seqKey(subjectId), String.valueOf(seq));
    }

    private String seqKey(Long subjectId) {
        return SEQ_KEY_PREFIX + subjectId;
    }

    private String decryptMessage(SessionArchivePullCommand command, FinanceMessage msg) {
        // 模拟 RSA 解密 encrypt_random_key；真实环境应使用 command.getPrivateKey() 解密
        String encryptKey = msg.getEncryptRandomKey();
        return financeSdkClient.decryptData(encryptKey, msg.getEncryptChatMsg());
    }

    private void saveMessageEvent(SessionArchivePullCommand command, FinanceMessage msg, String decryptedJson) {
        MessageEvent event = new MessageEvent();
        event.setEventId(msg.getMsgId());
        event.setTenantCode(command.getTenantCode());
        event.setChannelType("wecom");
        event.setChannelSubjectId(command.getChannelSubjectId());
        event.setDirection(1);
        event.setEventType("message");
        event.setMessageSource("session_archive");
        event.setContent(decryptedJson);
        event.setRawPayload(JsonUtils.toJson(Map.of(
                "seq", msg.getSeq(),
                "publicKeyVer", msg.getPublicKeyVer(),
                "encryptRandomKey", msg.getEncryptRandomKey(),
                "encryptChatMsg", msg.getEncryptChatMsg()
        )));
        event.setIsProcessed(0);
        event.setStatus(1);
        messageEventMapper.insert(event);
        inboundMessageService.processInboundMessage(event);
    }

    private void saveMediaIfNeeded(SessionArchivePullCommand command, FinanceMessage msg, String decryptedJson) {
        try {
            Map<String, Object> payload = OBJECT_MAPPER.readValue(decryptedJson, Map.class);
            String msgType = Optional.ofNullable(payload.get("msgtype")).map(Object::toString).orElse("");
            if (!isMediaType(msgType)) {
                return;
            }
            @SuppressWarnings("unchecked")
            Map<String, Object> mediaBody = (Map<String, Object>) payload.get(msgType);
            if (mediaBody == null) {
                return;
            }
            MessageMedia media = new MessageMedia();
            media.setTenantCode(command.getTenantCode());
            media.setMsgid(msg.getMsgId());
            media.setSdkfileid(Optional.ofNullable(mediaBody.get("sdkfileid")).map(Object::toString).orElse(null));
            media.setMediaType(msgType);
            media.setFileName(Optional.ofNullable(mediaBody.get("filename")).map(Object::toString).orElse(null));
            media.setFileSize(parseLong(mediaBody.get("filesize")));
            media.setMd5sum(Optional.ofNullable(mediaBody.get("md5sum")).map(Object::toString).orElse(null));
            media.setStatus(0);
            messageMediaMapper.insert(media);
        } catch (Exception e) {
            log.warn("[SessionArchive] 解析媒体消息失败 msgId={}", msg.getMsgId(), e);
        }
    }

    private boolean isMediaType(String msgType) {
        return "image".equals(msgType) || "voice".equals(msgType)
                || "video".equals(msgType) || "file".equals(msgType) || "emotion".equals(msgType);
    }

    private Long parseLong(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number n) {
            return n.longValue();
        }
        try {
            return Long.parseLong(value.toString());
        } catch (NumberFormatException e) {
            return null;
        }
    }
}
