/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.core.secure.interceptor;

import java.io.IOException;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springblade.common.openfeign.OpenFeignConstants;
import org.springblade.core.boot.properties.OpenFeignProperties;
import org.springblade.core.constant.BladeConstant;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.secure.utils.WebUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.api.ResultCode;
import org.springblade.core.tool.utils.NumberUtil;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.HandlerInterceptor;

import com.alibaba.fastjson2.JSON;

import cn.hutool.core.net.Ipv4Util;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 签名认证拦截器
 *
 * @author Chill
 */
@Slf4j
@AllArgsConstructor
public class TokenInterceptor implements HandlerInterceptor {

	private final OpenFeignProperties openFeignProperties;
	
	@Override
	public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
		String openfeignHeader = request.getHeader(OpenFeignConstants.HEADER_SYMBOL);
		log.debug("====>>>openfeignHeader:{}", openfeignHeader);
		//远程服务接口
		if(openfeignHeader != null && openfeignHeader.equals(OpenFeignConstants.HEADER_SYMBOL_VAL)) {
			//1.校验时间戳
			long tsHeaderVal = NumberUtil.toLong(request.getHeader(OpenFeignConstants.HEADER_TS), 0L);
			if(openFeignProperties.isCheckTimeError() && System.currentTimeMillis() - tsHeaderVal > openFeignProperties.getInvokeTimeErrorMills()) {
				log.info("时间戳校验失败|{}ms", System.currentTimeMillis() - tsHeaderVal);
				write(response, R.fail("服务校验失败"));
				return false;
			}
			//2.校验是否内网
			String ipAddress = ServletUtil.getClientIP(request);
			log.debug("ipAddress:{}", ipAddress);
			if(openFeignProperties.isCheckInnerIp() && !Ipv4Util.isInnerIP(ipAddress)) {
				log.info("内网IP校验失败|%s", ipAddress);
				write(response, R.fail("服务校验失败了"));
				return false;
			}
			//3.校验URI开头是否是服务名
			if(!StrUtil.startWithAny(request.getRequestURI().substring(1), openFeignProperties.getServiceName().stream().toArray(String[]::new))) {
				log.info("服务名校验失败|uri={},serviceNames={}", request.getRequestURI().substring(1), String.join(",", openFeignProperties.getServiceName()));
				write(response, R.fail("服务校验失败啦"));
				return false;
			}
		}else if (null == AuthUtil.getUser()) {
			log.info("签名认证失败，请求接口：{}，请求IP：{}，请求参数：{}", request.getRequestURI(), WebUtil.getIP(request), JSON.toJSONString(request.getParameterMap()));
			write(response, R.fail(ResultCode.UN_AUTHORIZED));
			return false;
		}
		return true;
	}
	
	void write(HttpServletResponse response, R<?> result) {
//		R result = R.fail(ResultCode.UN_AUTHORIZED);
		response.setCharacterEncoding(BladeConstant.UTF_8);
		response.addHeader(BladeConstant.CONTENT_TYPE_NAME, MediaType.APPLICATION_JSON_VALUE);
		response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
		try {
			response.getWriter().write(Objects.requireNonNull(JSON.toJSONString(result)));
		} catch (IOException ex) {
			log.error(ex.getMessage());
		}
	}
}