package com.eactive.eai.adapter.http.dynamic.filter.custom;

import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

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

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;

import com.eactive.eai.adapter.http.dynamic.HttpAdapterServiceSupport;
import com.eactive.eai.adapter.http.dynamic.filter.HttpAdapterFilter;
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
import com.eactive.eai.authserver.service.OAuth2Manager;
import com.eactive.eai.authserver.util.BeanUtils;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.inbound.action.ActionFactory;
import com.eactive.eai.inbound.action.RequestAction;
import com.eactive.eai.inbound.processor.Processor;
import com.eactive.eai.message.StandardMessageUtil;

public class SnapSimpleOauth2Filter implements HttpAdapterFilter {
	static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);

	private static final String SERVICE_CODE_UNKNOWN = "00";

	private TokenStore tokenStore;

	public SnapSimpleOauth2Filter() {
		super();
		tokenStore = BeanUtils.getBean("tokenStore", TokenStore.class);
	}

	@Override
	public Object doPreFilter(String adptGrpName, String adptName, Object message, Properties prop,
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		try {
			String apiId = assignApiId(adptGrpName, adptName, message, prop, request);
			if (StringUtils.isBlank(apiId)) {
				throw new JwtAuthException(
						String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
						"Can not find apiId(apiCode) info");
			}

			String tokenStr = extractToken(request);

			OAuth2AccessToken token = tokenStore.readAccessToken(tokenStr);
			if (token == null) {
				throw new JwtAuthException(
						String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "03"),
						"Token Not Found (B2B)");
			}

			if (token.isExpired()) {
				throw new JwtAuthException(
						String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
						"Invalid Token - expired (B2B)");
			}

			OAuth2Authentication authentication = tokenStore.readAuthentication(token);
			if (!authentication.isAuthenticated()) {
				throw new JwtAuthException(
						String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
						"Invalid Token (B2B)");
			}

			// check scope
			OAuth2Manager manager = OAuth2Manager.getInstance();
			HashSet<String> scopeSet = manager.getApiScopeMap().get(apiId);
			if (!verifyScope(scopeSet, authentication)) {
				throw new JwtAuthException(
						String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
						String.format("Insufficient [Scope](Allowed Scope=%s)", scopeSet.toString()));
			}

			// header 값으로 전송된 clientId와 토큰 소유 clientId check
			if (!verifyClientId(prop, authentication)) {
				throw new JwtAuthException(
						String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
						"client_id not matched(header/token)");
			}
		} catch (JwtAuthException e) {
			logger.debug(e.getMessage());
			throw e;
		} catch (Exception e) {
			logger.error(e.getMessage());
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "01"),
					"Invalid Token (B2B)");
		}

		return message;
	}

	private boolean verifyScope(HashSet<String> scopeSet, OAuth2Authentication auth) {
		if (scopeSet == null || scopeSet.isEmpty()) {
			return true; // If no scope is specified in the api, all are allowed
		}

		Set<String> scopesInToken = auth.getOAuth2Request().getScope();

		if (scopesInToken == null || scopesInToken.isEmpty()) {
			return false;
		}

		for (String scope : scopesInToken) {
			if (scopeSet.contains(scope)) {
				return true;
			}
		}

		return false;
	}

	private boolean verifyClientId(Properties prop, Authentication authentication) {
		try {
			// 이전 filter에서 셋팅한 clientId 확보
			String headerClientId = prop.getProperty(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID);
			String tokenClientId = ((OAuth2Authentication) authentication).getOAuth2Request().getClientId();

			// 이전 filter에서 셋팅한 값이 없다면 token 정보에서 확보
			if (StringUtils.isBlank(headerClientId)) {
				if (StringUtils.isNotBlank(tokenClientId)) {
					prop.put(HttpAdapterServiceSupport.PROPERTIES_NAME_CLIENT_ID, tokenClientId);
				}
			} else { // header로 전달된 clientId가 있을때만 비교
				if (!headerClientId.equals(tokenClientId)) {
					return false;
				}
			}
		} catch (Exception e) {
			logger.error(e.getMessage());
		}

		return true;
	}

	private String assignApiId(String adptGrpName, String adptName, Object message, Properties prop,
			HttpServletRequest request) {
		String apiId = null;
		try {
			String actionName = prop.getProperty(Processor.REQUEST_ACTION);
			RequestAction action = ActionFactory.createAction(actionName);
			action.setAdapterInfo(adptGrpName, adptName, prop);
			String[] keys = action.perform(message);
			apiId = keys[0];

			// PathVariable 지원 추가
			apiId = StandardMessageUtil.getMatchedKey(apiId, actionName);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}

//		// header 에서 확보
//		String apiId = request.getHeader(HEADER_NAME_API_CODE);
//		if (StringUtils.isBlank(apiId)) {
//			// url에서 확보
//			// /ONLWeb/api/v1/public/getUserInfo.svc/
//			apiId = request.getRequestURI();
//			// /ONLWeb/api/v1/public/getUserInfo.svc
//			apiId = StringUtils.removeEnd(apiId, "/");
//			// getUserInfo.svc
//			apiId = StringUtils.substringAfterLast(apiId, "/");
//		}

		return apiId;
	}

	public static String extractToken(HttpServletRequest request) throws JwtAuthException {
		String authorization = request.getHeader("Authorization");
		if (StringUtils.isBlank(authorization)) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
					"No have header info: Authorization");
		}

		String[] components = authorization.split("\\s");

		if (components.length != 2) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
					"Malformat [Authorization] content");
		}

		if (!StringUtils.equalsIgnoreCase(components[0], "Bearer")) {
			throw new JwtAuthException(
					String.format("%d%s%s", HttpStatus.UNAUTHORIZED.value(), SERVICE_CODE_UNKNOWN, "00"),
					"[Bearer] is needed");
		}

		return components[1].trim();
	}

	@Override
	public Object doPostFilter(String adptGrpName, String adptName, Object resultMessage, Properties prop,
			HttpServletRequest request, HttpServletResponse response) throws Exception {
		return resultMessage;
	}
}