package kr.co.uplus.ez.common.xss;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import kr.co.uplus.ez.common.data.Const;
import lombok.Data;

@Data
@Configuration
@ConfigurationProperties(prefix = "xssconfig")
@PropertySource(value = "classpath:xss-prevent.yml", factory = XssPreventFactory.class)
public class XssPreventer {

	private static final Logger log = LoggerFactory.getLogger(XssPreventer.class);

	public static String ESCAPE_TYPE_ALLOW = "allow";
	public static String ESCAPE_TYPE_EXCEPT = "except";
	public static String ESCAPE_TYPE; // allow, except
	public static List<String> EXCLUDE_URLS;
	public static List<EscapeCharacter> ESCAPE_CHARACTERS;
	public static List<String> ALLOW_ELEMENTS;
	public static List<EscapeCharacter> EXCEPT_ELEMENTS;

	@SuppressWarnings("static-access")
	public void setEscapeType(String escapeType) {
		this.ESCAPE_TYPE = escapeType;
	}

	@SuppressWarnings("static-access")
	public void setExcludeUrls(List<String> excludeUrls) {
		this.EXCLUDE_URLS = excludeUrls;
	}

	@SuppressWarnings("static-access")
	public void setEscapeCharacters(List<EscapeCharacter> escapeCharacters) {
		this.ESCAPE_CHARACTERS = escapeCharacters;
	}

	@SuppressWarnings("static-access")
	public void setAllowElements(String allowElements) {
		if (StringUtils.isNotBlank(allowElements)) {
			this.ALLOW_ELEMENTS = Arrays.asList(StringUtils.split(allowElements, ","));
		}
	}

	@SuppressWarnings("static-access")
	public void setExceptElements(List<EscapeCharacter> exceptElements) {
		this.EXCEPT_ELEMENTS = exceptElements;
	}

	public static String escape(String value) {
		// allow - escape-characters 를 모두 적용 후 allow-elements 만 <, > 치환
		if (StringUtils.equals(ESCAPE_TYPE, ESCAPE_TYPE_ALLOW)) {
			if (!CollectionUtils.isEmpty(ESCAPE_CHARACTERS)) {
				for (EscapeCharacter e : ESCAPE_CHARACTERS) {
					value = value.replaceAll(StringUtils.trim(e.getTarget()), StringUtils.trim(e.getTrans()));
				}
				// 허용태그 치환
				value = replaceAllowElem(value);
			}
			// except - escape-characters 를 적용하지 않고 except-elements 에 해당되면 공백문자로 치환
		} else if (StringUtils.equals(ESCAPE_TYPE, ESCAPE_TYPE_EXCEPT)) {
			value = replaceExceptElem(value);
		}
		return value;
	}

	public static String replaceExceptElem(String value) {
		if (StringUtils.isNotBlank(value)) {
			Pattern p;
			Matcher m;
			String targetString = "";
			String matchString = "";
			String replaceString = "";

			for (EscapeCharacter e : EXCEPT_ELEMENTS) {
				targetString = e.getTarget();
				replaceString = e.getTrans();
				p = Pattern.compile(targetString);
				m = p.matcher(value);
				while (m.find()) {
					matchString = m.group(0);
					value = value.replace(matchString, replaceString);
				}
			}
		}
		return value;
	}

	public static String replaceAllowElem(String value) {
		if (StringUtils.isNotBlank(value)) {
			Pattern p;
			Matcher m;
			String matchString = "";
			String replaceString = "";

			// 영문, 숫자를 제외한 태그 허용
			/*
			 * p = Pattern.compile("&lt;[\\W]*&gt;"); m = p.matcher(value); while(m.find())
			 * { matchString = m.group(0); replaceString = matchString.replaceFirst("&lt;",
			 * "<"); value = value.replace(matchString, replaceString);
			 *
			 * matchString = replaceString; replaceString = replaceLast(matchString, "&gt;",
			 * ">"); value = value.replace(matchString, replaceString); }
			 */

			// 허용 태그에 등록된있으면 허용
			for (String s : ALLOW_ELEMENTS) {
				s = StringUtils.trim(s);
				p = Pattern.compile("&lt;\\/?(?i)" + s + "(&gt;|\\s+((?!&gt;).)*?&gt;)");
				m = p.matcher(value);
				while (m.find()) {
					matchString = m.group(0);
					replaceString = matchString.replaceFirst("&lt;", "<");
					value = value.replace(matchString, replaceString);

					matchString = replaceString;
					replaceString = replaceLast(matchString, "&gt;", ">");
					value = value.replace(matchString, replaceString);
				}
			}
		}
		return value;
	}

	public static String unescape(String value) {
		if (!CollectionUtils.isEmpty(ESCAPE_CHARACTERS)) {
			for (EscapeCharacter e : ESCAPE_CHARACTERS) {
				if (StringUtils.isNotBlank(e.getTrans()) && StringUtils.equals(Const.COMM_YES, e.getUnescapeYn())) {
					value = value.replaceAll(e.getTrans(), e.getTarget());
				}
			}
		}
		return value;
	}

	@SuppressWarnings("unchecked")
	public static Map<String, Object> unescapeMap(Map<String, Object> params) {
		Map<String, Object> rtn = new HashMap<String, Object>();

		try {
			Gson gson = new GsonBuilder().disableHtmlEscaping().create();
			String jsonString = gson.toJson(params);

			String paramStr = unescape(jsonString);
			rtn = gson.fromJson(paramStr, HashMap.class);
		} catch (Exception e) {
			log.error("XssPreventer.unescapeMap Error : {}, params : {}", e, params);
			rtn = new HashMap<String, Object>(params);
		}

		return rtn;
	}

	private static String replaceLast(String string, String toReplace, String replacement) {
		String s = string.replaceFirst("(?s)(.*)" + toReplace, "$1" + replacement);
		return s;
	}

}
