package cn.axzo.msg.center.message.service.replay;

import cn.axzo.basics.common.exception.ServiceException;
import cn.axzo.msg.center.common.utils.BizAssertions;
import cn.axzo.msg.center.common.utils.MD5;
import cn.axzo.msg.center.dal.RequestReplayDao;
import cn.axzo.msg.center.domain.entity.RequestReplay;
import cn.axzo.msg.center.inside.notices.config.PendingMessageBizConfig;
import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.function.Supplier;

/**
 * @author yanglin
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RequestReplayService {

    private final RequestReplayDao requestReplayDao;
    private final PendingMessageBizConfig cfg;
    private final RedisTemplate<String, String> redisTemplate;

    public <T> T run(RequestInfo requestInfo, Supplier<T> supplier) {
        if (!isWindowEnabled(requestInfo))
            return supplier.get();
        if (acquire(requestInfo) == RequestAcquireResult.DUPLICATE_REQUEST)
            throw new ServiceException(403, "重复请求: " + requestInfo);
        try {
            return supplier.get();
        } catch (Exception e) {
            release(requestInfo);
            log.warn("run request error, requestInfo={}", requestInfo, e);
            throw e;
        }
    }

    private RequestAcquireResult acquire(RequestInfo info) {
        try {
            return acquireRequestImpl(info);
        } catch (Exception e) {
            log.warn("acquire request error, requestInfo={}", info, e);
            return RequestAcquireResult.NEW_REQUEST;
        }
    }

    private void release(RequestInfo requestInfo) {
        if (!isWindowEnabled(requestInfo))
            return;
        try {
            BizAssertions.assertNotNull(requestInfo, "requestInfo is null");
            redisTemplate.delete(buildKey(hash(requestInfo)));
        } catch (Exception e) {
            log.warn("release request error, requestInfo={}", requestInfo, e);
        }
    }

    private RequestAcquireResult acquireRequestImpl(RequestInfo requestInfo) {
        BizAssertions.assertNotNull(requestInfo, "info is null");
        BizAssertions.assertNotNull(requestInfo.getRequest(), "request is null");
        String hash = hash(requestInfo);
        RequestReplay replay = new RequestReplay();
        replay.setTemplateCode(requestInfo.getTemplateCode());
        replay.setBizCode(requestInfo.getBizCode());
        String subBizCode = requestInfo.getSubBizCode();
        replay.setSubBizCode(subBizCode == null ? "" : subBizCode);
        replay.setType(requestInfo.getReplayType());
        replay.setHash(hash);
        replay.setRequest(JSON.parseObject(JSON.toJSONString(requestInfo.getRequest())));
        try {
            requestReplayDao.save(replay);
        } catch (Exception e) {
            log.warn("save request error, requestInfo={}", requestInfo, e);
        }
        Duration timeout = Duration.ofSeconds(cfg.getRequestReplayWindowExpireTimeInSeconds());
        Boolean success = redisTemplate.opsForValue().setIfAbsent(buildKey(hash), "", timeout);
        RequestAcquireResult acquireResult = Boolean.TRUE.equals(success)
                ? RequestAcquireResult.NEW_REQUEST
                : RequestAcquireResult.DUPLICATE_REQUEST;
        if (acquireResult == RequestAcquireResult.DUPLICATE_REQUEST)
            log.warn("duplicate request, requestInfo={}", requestInfo);
        return acquireResult;
    }

    private boolean isWindowEnabled(RequestInfo requestInfo) {
        return cfg.isRequestReplayWindowEnabled()
                && !cfg.getRequestReplayWhitelistTemplateCodes()
                .contains(requestInfo.getTemplateCode());
    }

    private String hash(RequestInfo requestInfo) {
        String jsonString = JSON.toJSONString(requestInfo.getRequest());
        return MD5.getMD5Code(jsonString);
    }

    private static String buildKey(String hash) {
        return String.format("msg-center:request-replay:%s", hash);
    }

}