[RequestProxyFilter, order 4]

package com.ktds.act.apigw.filter;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

import java.net.URI;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.RouteMetadataUtils;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;

import com.ktds.act.apigw.api.db.entity.AdmApiDply;
import com.ktds.act.apigw.filter.function.SetExchangeRequest;
import com.ktds.act.apigw.filter.model.AgwErrorDto;
import com.ktds.act.apigw.filter.model.TransactionDto;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class RequestProxyFilter implements GlobalFilter, Ordered {

	@Autowired
	private ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter;

	@Autowired
	private SetExchangeRequest setExchangeRequest;

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		TransactionDto transaction = (TransactionDto) exchange.getAttribute(TransactionDto.TRANSACTION);
		//Thread.currentThread().setName(transaction.getComn().getTxId());
		log.info("RequestProxyFilter - Start");

		try {

			**//응용계층의 네트워크 프로토콜이 NONE이 아닐때만 작업을 시작한다. (어떤 프로토콜인지)
			//NONE이면 아무것도 하지 않고 넘어간다.**
			if(!"NONE".equals(transaction.getReq().getEdpt().getProt())){
			
			**//여러개 Edpt중에 원하는 걸로 chooseUrl한다????**
			Optional<String> endpoint = transaction.getReq().getEdpt().chooseUrl();
			// String endpoint = endpointService.getEndpoint(transaction.getReq().getEdpt());
			
			// uriOut을 통해 endpoint 도출 **(어디로 연동할지에 대한 정보를 세팅한다)**
			AdmApiDply api = transaction.getComn().getApi();
			String uriOut = setUriOut(transaction);
			String uriFullPath = endpoint.get() + uriOut;

			log.info("Route Endpoint : " + uriFullPath);

			**//routing 정보 재정의**
			// 연동포인트(Route) 정보 **(연동할때 timeout과 같은 옵션 세팅)**
			String routeId = "custom-route";
			Integer connectionTimeout = (int) api.getTimeOut();
			Number readTimeout = api.getTimeOut();

			// 연동포인트(Route) 변경 **(Yaml에서 그냥 뚫려 있던 Routing path들 재 세팅)**
			Route originalRoute = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
			Route newRoute = Route.async()
					.asyncPredicate(originalRoute.getPredicate())
					.id(routeId)
					.uri(uriFullPath)
					.metadata(RouteMetadataUtils.CONNECT_TIMEOUT_ATTR, connectionTimeout)
					.metadata(RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR, readTimeout)
					.build();

			**//exchange에 새로 갱신된 route정보를 덮어씌워준다. (이 부분이 완벽히 이해는 안감)**
			exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, newRoute);

			log.debug("exchange header before : {}",exchange.getRequest().getHeaders());
			
			**//변환된 정보를 재 정의 (request) -> 이전에 핸들러로 바뀔수 있으니까**
			URI newUri = exchange.getRequest().getURI(); **//requestURI**
			MultiValueMap<String, String> queryParam = transaction.getReq().getQueryParams();

			if((queryParam != null && !queryParam.isEmpty())
					|| !uriOut.equals(newUri.getPath())
					|| !exchange.getRequest().getMethod().matches(transaction.getReq().getMeth())) {

				if(queryParam != null && !queryParam.isEmpty()) {
					newUri = setNewUriWithQueryParams(exchange, transaction);
				}
				
				HttpMethod meth = HttpMethod.valueOf(transaction.getReq().getMeth());
				ServerHttpRequest serverHttpRequest =exchange.getRequest().mutate()
						.uri(newUri)
						.path(uriOut)
						.method(meth)
						.build();
				exchange = exchange.mutate().request( serverHttpRequest ).build();
				log.debug("exchange header after : {}",exchange.getRequest().getHeaders());

			}

			}
		} catch (NullPointerException e) {
			log.error("Invalid endpoint url", e);
			transaction.setError(AgwErrorDto.INTERNAL_SERVER_ERROR);
			return Mono.error(e);
		}catch (Exception e) {
			log.error("Invalid endpoint url", e);
			transaction.setError(AgwErrorDto.INTERNAL_SERVER_ERROR);
			return Mono.error(e);
		}
		log.info("RequestProxyFilter - End");
		
		//Transaction객체에 있는 request body 정보대로 exchange 객체의 request body 설정
		if(transaction.getReq().isBodyChg()){
			return modifyRequestBodyFilter
			.apply(
				new ModifyRequestBodyGatewayFilterFactory.Config()
					.setRewriteFunction(byte[].class, byte[].class, setExchangeRequest))
			.filter(exchange, chain);
		}
		transaction.setReqComplete();
		return  chain.filter(exchange);
	}

	private URI setNewUriWithQueryParams(ServerWebExchange exchange, TransactionDto transaction) {
		URI uri = exchange.getRequest().getURI();
		StringBuilder query = new StringBuilder();
		MultiValueMap<String, String> txQueryParams = transaction.getReq().getQueryParams();
		for(String key : txQueryParams.keySet()){
			query.append(key);
			query.append('=');
			query.append(txQueryParams.get(key).get(0));
			query.append('&');
		}
		return UriComponentsBuilder.fromUri(uri).replaceQuery(query.toString()).build(true).toUri();
	}

	private String setUriOut(TransactionDto transaction) {
		if(!StringUtils.hasText(transaction.getReq().getOut())){
			return transaction.getReq().getIn();
		} else {
			return transaction.getReq().getOut();
		}
	}

	@Override
	public int getOrder() {
		return 4;
	}
}