package com.codvision.webcore.config.encrypt.advice;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import com.codvision.commoncore.exception.BusinessException;
import com.codvision.webcore.config.encrypt.ApiSecretProperties;
import com.codvision.webcore.config.encrypt.ApiSecretSupports;
import com.codvision.webcore.config.encrypt.bean.ApiSecretHttpInputMessage;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * 请求数据解密处理
 * 仅对使用{@link org.springframework.web.bind.annotation.RequestBody}进行标记的参数生效
 *
 * @author hxl
 */
@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
@ConditionalOnProperty(value = ApiSecretProperties.PREFIX + ".enabled", havingValue = "true")
@ConditionalOnExpression("!'${security.api.encrypt.secret-key}'.isEmpty()")
public class ApiSecretRequestBodyAdvice implements RequestBodyAdvice {

    private final ObjectMapper objectMapper;
    private final ApiSecretProperties properties;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return ApiSecretSupports.supports(methodParameter, properties);
    }

    @Override
    @SneakyThrows
    @SuppressWarnings("unchecked")
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        InputStream messageBody = inputMessage.getBody();
        byte[] bodyByteArray = StreamUtils.copyToByteArray(messageBody);
        Map<String, Object> dataMap = objectMapper.readValue(bodyByteArray, Map.class);
        String content = (String) dataMap.get(ApiSecretProperties.PARAM_NAME);
        if (StringUtils.isBlank(content)) {
            throw new BusinessException(30001, "API接口加解密场景下，encryption参数要求非空！");
        }

        AES aes = new AES(Mode.CBC, Padding.ZeroPadding, new SecretKeySpec(properties.getSecretKey().getBytes(), "AES"),
                new IvParameterSpec(properties.getSecretKey().getBytes()));

        byte[] decryptedBody;
        try {
            decryptedBody = aes.decrypt(StrUtil.str(content.getBytes(StandardCharsets.UTF_8), Charset.defaultCharset()));
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new BusinessException(30001, "请检查encryption参数值是否已加密传输");
        }

        InputStream inputStream = new ByteArrayInputStream(decryptedBody);
        return new ApiSecretHttpInputMessage(inputStream, inputMessage.getHeaders());
    }

    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }
}
