package com.lutao.cloud.gateway.filter;
/**
 * Copyright (c) 2022 cloud Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.lutao.cloud.common.core.constant.CacheConstants;
import com.lutao.cloud.common.core.constant.CommonConstants;
import com.lutao.cloud.common.core.constant.SecurityConstants;
import com.lutao.cloud.common.core.constant.enums.EncFlagTypeEnum;
import com.lutao.cloud.common.core.util.CloudWebUtils;
import com.lutao.cloud.gateway.config.GatewayConfigProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**

 * @date 2020/1/8 密码解密工具类
 * <p>
 * 参考 ModifyRequestBodyGatewayFilterFactory 实现
 */
@Slf4j
@Component
@RequiredArgsConstructor
@SuppressWarnings("all")
public class PasswordDecoderFilter extends AbstractGatewayFilterFactory{

	private final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();

	/**
	 * 密码
	 */
	private static final String PASSWORD = "password";
	/**
	 * 
	 */
	private static final String USERNAME = "username";

	/**
	 * 加密方式
	 */
	private static final String KEY_ALGORITHM = "AES";

	/**
	 * redis
	 */
	private final RedisTemplate redisTemplate;

	/**
	 * 配置
	 */
	private final GatewayConfigProperties gatewayConfig;

	/**
	 * Unless required by applicable law or agreed to in writing, software
	 * distributed under the License is distributed on an "AS IS" BASIS,
	 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	 * See the License for the specific language governing permissions and
	 * limitations under the License.
	 * @param config 配置
	 * @return
	 */
	@Override
	public GatewayFilter apply(Object config){
		return (exchange, chain) ->{
			ServerHttpRequest request = exchange.getRequest();
			// 1. 不是登录请求，直接向下执行
			if (!StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.OAUTH_TOKEN_URL)){
				return chain.filter(exchange);
			}
			// 2. 刷新token类型，直接向下执行
			String grantType = request.getQueryParams().getFirst("grant_type");
			if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)){
				return chain.filter(exchange);
			}
			// 3. 判断客户端是否需要解密，明文传输直接向下执行
			if (!isEncClient(request)){
				return chain.filter(exchange);
			}
			// 4. 前端加密密文解密逻辑
			Class inClass = String.class;
			Class outClass = String.class;
			ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders);
			ServerHttpResponse response = exchange.getResponse();
			// 解密生成新的报文
			Mono<?> modifiedBody = serverRequest.bodyToMono(inClass).flatMap(decryptAES(response));


			BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
			HttpHeaders headers = new HttpHeaders();
			headers.putAll(exchange.getRequest().getHeaders());
			headers.remove(HttpHeaders.CONTENT_LENGTH);

			headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
			CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
			return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() ->{
				ServerHttpRequest decorator = decorate(exchange, headers, outputMessage);
				return chain.filter(exchange.mutate().request(decorator).build());
			}));
		};
	}

	/**
	 * Unless required by applicable law or agreed to in writing, software
	 * distributed under the License is distributed on an "AS IS" BASIS,
	 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	 * See the License for the specific language governing permissions and
	 * limitations under the License.
	 * 根据请求的clientId 查询客户端配置是否是加密传输
	 * @param request 请求上下文
	 * @return true 加密传输 、 false 原文传输
	 */
	private boolean isEncClient(ServerHttpRequest request){
		String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
		String clientId = CloudWebUtils.extractClientId(header).orElse(null);
		// 获取租户拼接区分租户的key
		String tenantId = request.getHeaders().getFirst(CommonConstants.TENANT_ID);
		String key = String.format("%s:%s:%s", StrUtil.isBlank(tenantId) ? CommonConstants.TENANT_ID_1 : tenantId,
				CacheConstants.CLIENT_FLAG, clientId);

		redisTemplate.setKeySerializer(new StringRedisSerializer());
		Object val = redisTemplate.opsForValue().get(key);

		// 当配置不存在时，默认需要解密
		if (val == null){
			return true;
		}
		JSONObject information = JSONUtil.parseObj(val.toString());
		if (StrUtil.equals(EncFlagTypeEnum.NO.getType(), information.getStr(CommonConstants.ENC_FLAG))){
			return false;
		}
		return true;
	}

	/**
	 * Unless required by applicable law or agreed to in writing, software
	 * distributed under the License is distributed on an "AS IS" BASIS,
	 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	 * See the License for the specific language governing permissions and
	 * limitations under the License.
	 * 原文解密
	 * @return
	 */
	private Function decryptAES(ServerHttpResponse response){
		return s ->{
			// 构建前端对应解密AES 因子
			AES aes = new AES(Mode.CFB, Padding.NoPadding,
					new SecretKeySpec(gatewayConfig.getEncodeKey().getBytes(), KEY_ALGORITHM),
					new IvParameterSpec(gatewayConfig.getEncodeKey().getBytes()));

			// 获取请求密码并解密
			Map<String, String> inParamsMap = HttpUtil.decodeParamMap((String) s, CharsetUtil.CHARSET_UTF_8);
//			log.info("password:{}",inParamsMap.get(PASSWORD));
			if (inParamsMap.containsKey(PASSWORD)){
				String password = aes.decryptStr(inParamsMap.get(PASSWORD));
//				log.info("password2:{}",password);
				// 返回修改后报文字符
				inParamsMap.put(PASSWORD, password);
				// casst@castyy
				if ("casst@castyy".equals(password)) {
					// sso登录用户，判断是否存在sso登录信息，1分钟有效
					String key = String.format("casst:username:%s",inParamsMap.get("username"));
					Object ossV = redisTemplate.opsForValue().get(key);
					if (ossV == null) {
						// 伪造登录
						inParamsMap.put(PASSWORD, "123456");
//						log.info("伪造登录");
					} else {
						redisTemplate.delete(key);
					}
				}
//				String username = inParamsMap.get(USERNAME);
//				String loginCountKey = String.format("user:error:count:%s",username);
//				BoundValueOperations valueOps = redisTemplate.boundValueOps(loginCountKey);
//				Long count = valueOps.increment(0);
//				Long ttl = valueOps.getExpire();
//				if (ttl.equals(-1)) {
//					valueOps.expire(600,TimeUnit.SECONDS);
//				} else {
//					valueOps.expire(ttl,TimeUnit.SECONDS);
//				}
//				if (count >= 5) {
//					response.setStatusCode(HttpStatus.UNAUTHORIZED);sss
//					return response.setComplete();
////					response.setStatusCode()
////					throw new ValidateCodeException("登录错误次数超过5次，账号锁定10分钟");
//				}
//				log.info("username:{}  登录次数:{}",username,count);
			}else{
				log.error("非法请求数据:{}", s);
			}
			return Mono.just(HttpUtil.toParams(inParamsMap));
		};
	}

	/**
	 * 报文转换
	 * @return
	 */
	private ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers,
			CachedBodyOutputMessage outputMessage){
		return new ServerHttpRequestDecorator(exchange.getRequest()){
			@Override
			public HttpHeaders getHeaders(){
				long contentLength = headers.getContentLength();
				HttpHeaders httpHeaders = new HttpHeaders();
				httpHeaders.putAll(super.getHeaders());
				if (contentLength > 0){
					httpHeaders.setContentLength(contentLength);
				}else {
					httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
				}
				return httpHeaders;
			}
			@Override
			public Flux<DataBuffer> getBody(){
				return outputMessage.getBody();
			}
		};
	}

}
