package com.njcn.msgpush.module.push.client.sender;

import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.module.push.constant.MsgPushConstant;
import com.njcn.msgpush.module.push.constant.MsgStatusConstant;
import com.njcn.msgpush.module.push.dal.dataobject.channel.ProviderErrorCodeMappingDO;
import com.njcn.msgpush.module.push.dal.dataobject.message.MessageRecordDO;
import com.njcn.msgpush.module.push.dal.mysql.message.MessageRecordMapper;
import com.njcn.msgpush.module.push.dal.redis.MessageConfirmRedisDAO;
import com.njcn.msgpush.module.push.service.channel.ChannelProviderConfigService;
import com.njcn.msgpush.module.push.service.channel.ProviderErrorCodeMappingService;
import com.njcn.msgpush.module.push.service.retry.MessageRetryHistoryService;
import com.njcn.msgpush.module.push.service.retry.MessageRetryQueueService;
import com.njcn.msgpush.module.push.util.RestTemplateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Component
public class Sender {

    @Autowired
    private ChannelProviderConfigService channelProviderConfigService;
    @Autowired
    private MessageRetryQueueService messageRetryQueueService;
    @Autowired
    private ProviderErrorCodeMappingService providerErrorCodeMappingService;
    @Autowired
    public RestTemplateUtil restTemplateUtil;
    @Autowired
    private MessageRetryHistoryService messageRetryHistoryService;
    @Autowired
    private MessageRecordMapper messageRecordMapper;

    @Autowired
    private MessageConfirmRedisDAO messageConfirmRedisDAO;

    public final ThreadPoolExecutor MSG_PUSH_THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            5,
            5,
            1000,
            TimeUnit.MILLISECONDS,
            new java.util.concurrent.ArrayBlockingQueue<>(1000),
            r -> {
                Thread thread = new Thread(r);
                thread.setName("msgPushThreadPool-" + thread.getId());
                thread.setDaemon(false);
                return thread;
            },
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    public final ScheduledExecutorService MSG_CALLBACK_THREAD_POOL_SCHEDULER = Executors.newScheduledThreadPool(5);

    /**
     * 将服务商原始错误码映射为平台统一错误结果。
     * 这里不直接修改消息状态，只负责产出统一 SendResult。
     */
    public SendResult buildFailureResult(MessageRecordDO message,
                                         String providerRawCode,
                                         String providerRawMessage,
                                         String defaultErrorCode,
                                         String defaultMessage,
                                         boolean retryableFallback) {
        ProviderErrorCodeMappingDO mappingDO = null;
        if (StrUtil.isNotBlank(providerRawCode)) {
            mappingDO = this.providerErrorCodeMappingService.getByProviderErrorCode(
                    message.getProviderType(), message.getChannel(), providerRawCode);
        }

        boolean retryable = retryableFallback;
        String errorCode = StrUtil.blankToDefault(providerRawCode, defaultErrorCode);
        String resultMessage = StrUtil.blankToDefault(providerRawMessage, defaultMessage);

        if (mappingDO != null) {
            retryable = mappingDO.getShouldRetry() != null && mappingDO.getShouldRetry() == 1;
            errorCode = StrUtil.blankToDefault(mappingDO.getUnifiedErrorCode(), errorCode);
            resultMessage = StrUtil.blankToDefault(
                    mappingDO.getUnifiedErrorMessage(),
                    StrUtil.blankToDefault(mappingDO.getOriginalMessage(), resultMessage)
            );
        }

        if (StrUtil.isBlank(errorCode)) {
            errorCode = "PROVIDER_ERROR";
        }
        if (StrUtil.isBlank(resultMessage)) {
            resultMessage = StrUtil.blankToDefault(defaultMessage, MsgPushConstant.ERROR_MSG_UNKNOWN);
        }

        return SendResult.builder()
                .outcome(retryable ? SendOutcome.RETRYABLE_FAILED : SendOutcome.FAILED)
                .errorCode(errorCode)
                .message(resultMessage)
                .providerRawCode(providerRawCode)
                .retryable(retryable)
                .build();
    }

    public SendResult buildTimeoutResult() {
        return SendResult.builder()
                .outcome(SendOutcome.RETRYABLE_FAILED)
                .errorCode("THIRD_PARTY_TIMEOUT")
                .message("第三方服务调用超时")
                .retryable(true)
                .build();
    }

    public SendResult buildCallFailedResult(String message) {
        return SendResult.builder()
                .outcome(SendOutcome.RETRYABLE_FAILED)
                .errorCode("THIRD_PARTY_CALL_FAILED")
                .message(message)
                .retryable(true)
                .build();
    }

    /**
     * 异步回执场景下，沿用和主流程一致的状态落库方式。
     * 这样可以保证同步发送结果和异步回执结果使用同一套状态语义。
     */
    public void applyCallbackResult(MessageRecordDO message, SendResult sendResult) {
        String status = this.mapOutcomeToStatus(sendResult.getOutcome());
        message.setStatus(status);
        message.setErrorCode(sendResult.getErrorCode());
        message.setErrorMsg(sendResult.getMessage());

        this.messageConfirmRedisDAO.removeFromConfirmQueue(message);
        if (SendOutcome.SUCCESS.equals(sendResult.getOutcome())) {
            this.messageRetryQueueService.deleteByMessageIds(Collections.singletonList(message.getId()));
            this.channelProviderConfigService.successUpdate(message.getProviderType(), message.getChannel());
            message.setNextRetryTime(null);
        } else if (SendOutcome.RETRYABLE_FAILED.equals(sendResult.getOutcome())) {
            this.messageRetryQueueService.saveOrUpdateRetryMessage(message);
            this.channelProviderConfigService.failureUpdate(message.getProviderType(), message.getChannel());
        } else if (SendOutcome.FAILED.equals(sendResult.getOutcome())) {
            this.messageRetryQueueService.deleteByMessageIds(Collections.singletonList(message.getId()));
            this.channelProviderConfigService.failureUpdate(message.getProviderType(), message.getChannel());
        }

        this.messageRecordMapper.updateStatusAndErrorInfo(
                message.getId(),
                message.getStatus(),
                message.getErrorCode(),
                message.getErrorMsg(),
                message.getLastRetryTime(),
                message.getNextRetryTime()
        );
        this.messageRetryHistoryService.updateStatusAndErrorInfo(
                message.getId(),
                message.getStatus(),
                message.getErrorCode(),
                message.getErrorMsg()
        );
    }

    private String mapOutcomeToStatus(SendOutcome outcome) {
        if (outcome == null) {
            return MsgStatusConstant.FAILED;
        }
        return switch (outcome) {
            case SUCCESS -> MsgStatusConstant.SUCCESS;
            case ACCEPTED -> MsgStatusConstant.SENDING;
            case FAILED -> MsgStatusConstant.FAILED;
            case RETRYABLE_FAILED -> MsgStatusConstant.RETRYABLE_FAILED;
            case UNSUPPORTED_CHANNEL -> MsgStatusConstant.UNSUPPORTED_CHANNEL;
            case CONFIG_INVALID -> MsgStatusConstant.CONFIG_INVALID;
        };
    }

}
