package link.at17.mid.tushare.robot.feishu.ablilities;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletResponse;
import link.at17.mid.tushare.robot.models.FeishuAppEnv;
import link.at17.mid.tushare.system.util.SpringContextHolder;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonInputMessage;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import com.alibaba.fastjson2.JSONObject;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@ControllerAdvice("link.at17.mid.tushare.robot.feishu.controller")
public class FeishuAppControllerAdvice extends RequestBodyAdviceAdapter  {

    @Autowired
    HttpServletResponse response;
    
    private static MessageDigest sha256Digest = null;
    
    @PostConstruct
    private void init() throws NoSuchAlgorithmException {
        sha256Digest = MessageDigest.getInstance("SHA-256");
    }
    
    private FeishuAppEnv getEnv(Class<? extends FeishuAppEnv> clazz) {
        return SpringContextHolder.getBean(clazz);
    }

    /**
     * 飞书 App 校验错误处理
     * @param ex
     * @return
     */
    @ExceptionHandler(value = FeishuAppThrowable.class)
    @ResponseBody
    public Serializable feishuAppVerifyExceptionHandler(FeishuAppThrowable ex) {
        JSONObject respJson = new JSONObject();
        if (!ex.hasResponse()) {
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            respJson.put("error_info", ex.getMessage());
            return respJson;
        }
        else {
            return ex.getResponse();
        }
    }
    
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType,
            Class<? extends HttpMessageConverter<?>> converterType) {
        // 是否支持注入
        FeishuAppVerify feishuAppVerify = 
                methodParameter.getMethod()
                .getAnnotation(FeishuAppVerify.class);
        // 首先是验证注解不为空，而后获取环境配置的 encryptKey 不为空
        return Objects.nonNull(feishuAppVerify);
    }
    
    /**
     * 重写，用于验证和解密
     */
    @SuppressWarnings("serial")
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        HttpHeaders headers = inputMessage.getHeaders();
        if (!headers.getContentType().toString().startsWith(MediaType.APPLICATION_JSON.toString())
        ) {
            return inputMessage;
        }
        try {

            FeishuAppVerify feishuAppVerify = 
                    parameter.getMethod()
                    .getAnnotation(FeishuAppVerify.class);
            FeishuAppEnv env = getEnv(feishuAppVerify.value());
            InputStream inputStream = inputMessage.getBody();

            String body = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
            JSONObject json = JSONObject.parseObject(body);
            if (env.supportDecryption()) {
                try {
                    json = JSONObject.parseObject(env.decrypt(JSONObject.parseObject(body).getString("encrypt")));
                }
                catch (Exception e) {
                    throw new FeishuAppThrowable("decrypt error");
                }
            }
            
            if (Objects.isNull(json)) {
                // 本是加密的配置，却传来明文，或者主体根本无法获取
                throw new FeishuAppThrowable("bad request");
            }
            
            // if it's a challenge
            if (json.containsKey("type") && "url_verification".equals(json.getString("type"))) {
                final String challenge = json.getString("challenge");
                throw new FeishuAppThrowable("challenge").setResponse(new JSONObject() {{ put("challenge", challenge);}});
            }
            
            // schema verify
            if (!json.containsKey("schema")) {
                throw new FeishuAppThrowable("request is not callback event(v2)");
            }
            
            // Token verify
            if (!json.getJSONObject("header").getString("token").equals(env.getVerificationToken())) {
                throw new FeishuAppThrowable("invalid token");
            }

            String timestampStr = headers.getFirst("X-Lark-Request-Timestamp");
            
            // Timestamp expire verify
            int expireIn = feishuAppVerify.timestampExpireIn();
            if (expireIn > -1) {
                int timestampInt = Integer.parseInt(timestampStr);
                int currentTs = (int) (System.currentTimeMillis() / 1000);
                if (currentTs - timestampInt > expireIn) {
                    throw new FeishuAppThrowable("timestamp expired");
                }
            }
            
            String nonce = headers.getFirst("X-Lark-Request-Nonce");
            String signature = headers.getFirst("X-Lark-Signature");
            // Signature verify
            String localSignature = Hex.encodeHexString(
                sha256Digest.digest(
                    (timestampStr + nonce + env.getEncryptKey() + body).getBytes(StandardCharsets.UTF_8)));
            if (!localSignature.equalsIgnoreCase(signature)) {
                throw new FeishuAppThrowable("invalid signature in event");
            }
            return new MappingJacksonInputMessage(new ByteArrayInputStream(json.toJSONString().getBytes()), inputMessage.getHeaders());
        }
        catch (FeishuAppThrowable e) {
            log.error(e.getMessage());
            throw e;
        }
        catch (Exception e) {
            log.error("Error occurred. ", e);
            throw new FeishuAppThrowable("bad request");
        }
    }

}
