/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.crunii.com</a>
 */
package com.crunii.micro.gateway.filter.web;

import cn.hutool.core.convert.Convert;
import cn.hutool.json.JSONUtil;
import com.crunii.micro.common.constants.CommonConstants;
import com.crunii.micro.common.utils.*;
import com.crunii.micro.domain.dto.TsmUrlDto;
import com.crunii.micro.domain.dto.Worker;
import com.crunii.micro.domain.entitysql.main.TCallLogS;
import com.crunii.micro.domain.service.CurrWorkerService;
import com.crunii.micro.gateway.GatewayConstants;
import com.crunii.micro.gateway.filter.web.dt.RequestLoggingDecorator;
import com.crunii.micro.gateway.filter.web.dt.ResponseLoggingDecorator;
import com.crunii.micro.gateway.queue.CallLogHandler;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.*;
import reactor.core.publisher.Mono;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 田平 create 2019年12月26日上午10:19:02
 */
@Component
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
public class CallLogFilter implements WebFilter {

	private AntPathMatcher pathMatcher = new AntPathMatcher();

	@Autowired
	private CurrWorkerService currWorkerService;

	@Autowired
	private CallLogHandler callLogHandler;

	public static void main(String[] args) {
		AntPathMatcher matcher = new AntPathMatcher();
		String pattern = "/**/auth/**";
		System.out.println(matcher.match(pattern, "/auth/xxx"));
		System.out.println(matcher.match(pattern, "/xxx"));
		System.out.println(matcher.match(pattern, "/xxx/auth"));
		System.out.println(matcher.match(pattern, "/xxx/bbb/auth/aaa"));
		System.out.println(matcher.match("/xxx/auth/aaa", "/xxx/auth/aaa"));
		String s = FileUtil.getFileContent("C:\\Users\\Administrator\\Desktop\\userJson.txt");
		String json = Base64Util.encode(s);
		System.out.println(json);
	}

	/**
	 * @see org.springframework.web.server.WebFilter#filter(org.springframework.web.server.ServerWebExchange, org.springframework.web.server.WebFilterChain)
	 */
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
		long start = System.currentTimeMillis();
		boolean succ = pathMatcher.match(CommonConstants.AUTH_PATTERN, exchange.getRequest().getURI().getRawPath());
		Map<String, Object> currMap = new HashMap<>();
		LoginUser loginUser = new LoginUser();
		if (succ) {
			String token = exchange.getRequest().getHeaders().getFirst(CommonConstants.HEADER_TOKEN);
			if (!StringUtils.hasText(token)) {
				token = exchange.getRequest().getQueryParams().getFirst(CommonConstants.HEADER_TOKEN);
			}
			if (StringUtils.hasText(token)) {
				log.info("has token ");
				Worker worker = currWorkerService.getCurrWorker(token);
				if (worker != null) {
					log.info("worker !=null and put  :{}",worker.getLoginId());
					// header中排除权限相关的属性,只copy基础属性，减少传输大小
					BasePropCopyUtil.copyBasePropsToMap(worker, currMap);
					exchange.getAttributes().put(GatewayConstants.CURRENT_USER, worker);
					loginUser.setId(Convert.toLong(worker.getLoginId()));
				}
			}
		}

		ServerHttpRequest mutateRequest = exchange.getRequest().mutate().headers(headers -> {
			headers.add(CommonConstants.HEADER_IS_NEED_AUTH_URL, String.valueOf(succ));
			if (!currMap.isEmpty()) {
				String jsonHeader = JsonUtil.getJsonString(currMap);
				headers.add(CommonConstants.HEADER_CURRENT_USER, Base64Util.encodeUrl(jsonHeader));
				headers.add(GatewayConstants.LOGIN_USER_HEADER, JSONUtil.toJsonStr(loginUser));
			}
		}).build();

		exchange.getAttributes().put(GatewayConstants.IS_AUTH_URL, succ);
		exchange.getAttributes().put(GatewayConstants.CALL_LOG_FILTER_START_TIME, start);

		ServerWebExchange mutateExchange = exchange.mutate().request(mutateRequest).build();

		ServerWebExchangeDecorator exchangeDt = new ServerWebExchangeDecorator(mutateExchange) {
			@Override
			public ServerHttpRequest getRequest() {
				return new RequestLoggingDecorator(mutateRequest, mutateExchange.getAttributes());
			}

			@Override
			public ServerHttpResponse getResponse() {
				return new ResponseLoggingDecorator(exchange.getResponse(), mutateExchange.getAttributes());
			}
		};

		return chain.filter(exchangeDt).transformDeferred(call -> filter(exchangeDt, call));

	}

	private Publisher<Void> filter(ServerWebExchange exchange, Mono<Void> call) {
		long start = exchange.getAttributeOrDefault(GatewayConstants.CALL_LOG_FILTER_START_TIME, System.currentTimeMillis());
		ServerHttpResponse response = exchange.getResponse();
		String url = exchange.getRequest().getURI().getRawPath();
		boolean succ = exchange.getAttribute(GatewayConstants.IS_AUTH_URL);
		if (succ) {
			String token = exchange.getRequest().getHeaders().getFirst(CommonConstants.HEADER_TOKEN);
			if (!StringUtils.hasText(token)) {
				token = exchange.getRequest().getQueryParams().getFirst(CommonConstants.HEADER_TOKEN);
			}
			if (StringUtils.hasText(token)) {
				Worker worker = exchange.getAttribute(GatewayConstants.CURRENT_USER);
				log.info("{},worker is null? {}",url,worker != null);
				if (worker != null) {
					log.info("worker:{}",worker.getLoginId());
					//采用防止重放攻击来处理各种越权及漏洞，url权限暂时屏蔽
//					if (token.startsWith(CommonConstants.SCOPE_WEB)) {
//						if (!CommonConstants.SUPER_LOGIN_IDS.contains(worker.getLoginId())) {
//							List<TsmUrlDto> authUrls = worker.getUrls();
//							boolean authed = false;
//							if (!CollectionUtils.isEmpty(authUrls)) {
//								authed = authUrls.stream().anyMatch(tsmUrl -> {
//									if (tsmUrl.getUrlType() == 1) {
//										return tsmUrl.getUrlPath().equals(url);
//									} else {
//										return pathMatcher.match(tsmUrl.getUrlPath(), url);
//									}
//								});
//							}
//							if (!authed) {
//								return doLog(response, exchange, start, unauthUrl(exchange));
//							}
//						}
//					}
				} else {
					log.info("worker is null");

					return doLog(response, exchange, start, unauth(exchange));
				}
			} else {
				return doLog(response, exchange, start, unauth(exchange));
			}
		}
		return doLog(response, exchange, start, call);
	}

	private Mono<Void> doLog(ServerHttpResponse response, ServerWebExchange exchange, long start, Mono<Void> call) {
		return call.doOnSuccess(done -> success(exchange, start)).doOnError(cause -> error(exchange, start, cause));
	}

	private void success(ServerWebExchange exchange, long start) {
		TCallLogS callLog = getCallLog(exchange, start, null);
		callLogHandler.doCallLog(callLog);
		// log.info(callLog.toString() + " success");
	}

	private void error(ServerWebExchange exchange, long start, Throwable cause) {
		TCallLogS callLog = getCallLog(exchange, start, cause);
		callLogHandler.doCallLog(callLog);
		//log.error(callLog.toString() + " failure!", cause);
	}

	public TCallLogS getCallLog(ServerWebExchange exchange, long start, Throwable cause) {
		ServerHttpRequest req = exchange.getRequest();
		ServerHttpResponse rsp = exchange.getResponse();

		TCallLogS callLog = new TCallLogS();
		HttpStatus status = null;
		if (cause != null) {
			String simpleName = cause.getClass().getSimpleName();
			callLog.setExceptionName(StringUtils.hasText(simpleName) ? simpleName : cause.getClass().getName());
			if (cause instanceof ResponseStatusException) {
				status = ((ResponseStatusException) cause).getStatus();
			}
		}
		if (status == null) {
			status = rsp.getStatusCode();
		}

		callLog.setHttpStatus(status != null ? status.toString() : HttpStatus.OK.toString());
		callLog.setHttpMethod(req.getMethodValue());
		String realPath = (String) exchange.getAttributes().getOrDefault(GatewayConstants.REAL_PATH, req.getURI().getRawPath());
		callLog.setHttpUrl(realPath);
		callLog.setHttpOutcome(outcome(status));

		Worker worker = (Worker) exchange.getAttributes().get(GatewayConstants.CURRENT_USER);
		if (worker != null) {
			callLog.setLoginId(worker.getLoginId());
			callLog.setLoginCode(worker.getLoginCode());
			callLog.setWorkerName(worker.getWorkerName());
		}

		callLog.setIpAddr(IPUtilReactive.getIPAddr(req));
		callLog.setCallTime(new Date(start));
		callLog.setUseTime(System.currentTimeMillis() - start);

		// 请求
		Map<String, Object> recv = new HashMap<String, Object>();
		recv.put("headers", req.getHeaders());
		recv.put("queryParams", req.getQueryParams());
		recv.put("body", exchange.getAttribute(RequestLoggingDecorator.REQ_BODY_KEY));

		callLog.setRecvData(JsonUtil.getJsonString(recv));

		// 响应
		Map<String, Object> send = new HashMap<String, Object>();
		send.put("headers", rsp.getHeaders());
		send.put("body", exchange.getAttribute(ResponseLoggingDecorator.RSP_BODY_KEY));

		callLog.setSendData(JsonUtil.getJsonString(send));

		return callLog;
	}

	private static String outcome(HttpStatus status) {
		if (status != null) {
			if (status.is1xxInformational()) {
				return "INFORMATIONAL";
			}
			if (status.is2xxSuccessful()) {
				return "SUCCESS";
			}
			if (status.is3xxRedirection()) {
				return "REDIRECTION";
			}
			if (status.is4xxClientError()) {
				return "CLIENT_ERROR";
			}
			return "SERVER_ERROR";
		}
		return "UNKNOWN";
	}

	private Mono<Void> unauth(ServerWebExchange exchange) {
		exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
		return exchange.getResponse().setComplete();
	}

	private Mono<Void> unauthUrl(ServerWebExchange exchange) {
		exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
		return exchange.getResponse().setComplete();
	}

}
