package incheon.com.config;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.http.client.HttpClient;
import org.apache.http.message.BasicHeader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * 로컬개발 Proxy Configuration.<br>
 * spring.profiles.active @Profile({"DEV", "dev", "LOCAL", "local"})
 *
 * @see
 * <a href="https://github.com/mitre/HTTP-Proxy-Servlet/tree/smiley-http-proxy-servlet-1.12.1">Smiley's
 * HTTP Proxy Servlet</a>
 */
@Configuration
@Profile({"DEV", "dev", "LOCAL", "local"})
public class LocalProxyServletConfig {
	@Value("${gis.manager.url}")
    private String MAPPRIME_3D_MANAGER_PATH;
    @Value("${gis.build.url}")
    private String MAPPRIME_BUILDER_PATH;
    
	/**
	 * ProxyServlet을 등록합니다.
	 *
	 * {@link org.mitre.dsmiley.httpproxy.ProxyServlet} 참고
	 *
	 * @see org.mitre.dsmiley.httpproxy.ProxyServlet
	 * @throws java.net.URISyntaxException
	 * @return ServletRegistrationBean
	 */
	@Bean
	public ServletRegistrationBean<Servlet> localProxyServletRegistrationBean(HttpClient httpClient) throws URISyntaxException {
		/**
		 * 백엔드 맵핑을 아래와 같은 패턴으로 추가해주세요.
		 */
		Map<String, URI> localProxyMappings = new LinkedHashMap<>();
		localProxyMappings.put("/MapPrimeServer/rest/*", new URI("http://10.100.232.241:3004/MapPrimeServer/rest")); // 맵프라임2D
		localProxyMappings.put("/MapPrimeServer/map/wms", new URI("http://10.100.232.241:3004/MapPrimeServer/map/wms")); // 맵프라임2D
		localProxyMappings.put("/MapPrimeServer/map/wfs", new URI("http://10.100.232.241:3004/MapPrimeServer/map/wfs")); // 맵프라임2D
		localProxyMappings.put("/MapPrimeServer/map/wmts", new URI("http://10.100.232.241/MapPrimeServer/map/wmts")); // 맵프라임2D
		localProxyMappings.put("/POISearch/*", new URI("http://10.100.232.242:5000")); // POI 검색 
		localProxyMappings.put("/app/search/*", new URI("http://10.100.232.241:8983/app/search")); // 주소 검색
		
		localProxyMappings.put("/MapPrime3DBuilder/*", new URI(MAPPRIME_BUILDER_PATH)); // 맵프라임 3D 빌더
		
		// EX) 인천시 terrain : http://localhost:8080/MapPrime3DManager/root/incheon/base/terrain/icn_dem_1m/layer.json
		// EX) 3D 건물 : http://localhost:8080/MapPrime3DManager/root/incheon/base/tileset/in_build/tileset.json
		// EX) 포인트 클라우드 : http://localhost:8080/MapPrime3DManager/root/incheon/base/pointcloud/sample/tileset.json
		localProxyMappings.put("/MapPrime3DManager/root/*", new URI(MAPPRIME_3D_MANAGER_PATH + "/root")); // 맵프라임 3D 매니저
		// EX) 인천시 정사영상 : http://localhost:8080/cmm/g3f/image/map/wmts?LAYER=mapprime:incheon_2024_12cm&STYLE=&TILEMATRIXSET=google_tms&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&FORMAT=image/webp&TILEMATRIX=15&TILECOL=12700&TILEROW=27911
		localProxyMappings.put("/cmm/g3f/windServer/*", new URI("http://10.100.232.242:4444")); // 3D 바람길 서버 Proxy

		// localProxyMappings.put("/sgp/weather/*", new URI("https://apihub.kma.go.kr"));

		localProxyMappings.put("/aip/ows", new URI("http://10.100.232.247:803/cgi-bin/mapserv.exe"));

		ServletRegistrationBean<Servlet> srb = new ServletRegistrationBean<>(new MultipleProxyServlet(localProxyMappings, httpClient), localProxyMappings.keySet().toArray(String[]::new));
		srb.addInitParameter(org.mitre.dsmiley.httpproxy.ProxyServlet.P_LOG, "false");
		return srb;
	}

	/**
	 * 개발서버에서 resources 데이터를 가져옵니다.
	 *
	 * @return
	 */
	@Bean
	@Conditional(OnNotExistsResourcesDirectoryCondition.class)
	public FilterRegistrationBean<Filter> resourcesFilterRegistrationBean(HttpClient httpClient) {
		FilterRegistrationBean<Filter> registrationBean = new FilterRegistrationBean<>();
		registrationBean.setFilter(new OncePerRequestFilter() {

			private final Servlet proxyServlet = new org.mitre.dsmiley.httpproxy.ProxyServlet() {
				@Override
				protected HttpClient createHttpClient() {
					return httpClient;
				}
				@Override
				public void init() throws ServletException {
					super.hopByHopHeaders.addHeader(new BasicHeader("Cache-Control", null));
					super.hopByHopHeaders.addHeader(new BasicHeader("Pragma", null));
					super.hopByHopHeaders.addHeader(new BasicHeader("Expires", null));
					super.hopByHopHeaders.addHeader(new BasicHeader("Vary", null));
					super.hopByHopHeaders.addHeader(new BasicHeader("Server", null));
					super.init();
				}
			};

			@Override
			protected void initFilterBean() throws ServletException {
				FilterConfig filterConfig = getFilterConfig();
				proxyServlet.init(new ServletConfig() {
					@Override
					public String getServletName() {
						return filterConfig.getFilterName();
					}

					@Override
					public ServletContext getServletContext() {
						return filterConfig.getServletContext();
					}

					@Override
					public String getInitParameter(String name) {
						return filterConfig.getInitParameter(name);
					}

					@Override
					public Enumeration<String> getInitParameterNames() {
						return filterConfig.getInitParameterNames();
					}
				});
			}

			@Override
			public void destroy() {
				proxyServlet.destroy();
			}

			@Override
			protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
				if (request.getRequestURI().startsWith("/resources/")) {
					response.setHeader("Cache-Control", "max-age=3600, public");
					request.setAttribute(org.mitre.dsmiley.httpproxy.ProxyServlet.class.getSimpleName() + ".targetUri", ((HttpServletRequest) request).getRequestURI());
					proxyServlet.service(request, response);
				} else {
					filterChain.doFilter(request, response);
				}
			}

		});

		registrationBean.addInitParameter(org.mitre.dsmiley.httpproxy.ProxyServlet.P_TARGET_URI, "http://10.100.232.211:8080");
		registrationBean.addInitParameter(org.mitre.dsmiley.httpproxy.ProxyServlet.P_LOG, "false");
		registrationBean.addUrlPatterns("/resources/*");
		registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE - 1);
		return registrationBean;
	}
}

/**
 * resources 폴더 존재유무 확인
 */
class OnNotExistsResourcesDirectoryCondition implements Condition {

	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		Environment env = context.getEnvironment();
		Path resourcesPath = Paths.get(env.getProperty("Globals.resources.path", String.class));
		return Files.notExists(resourcesPath) || !Files.isDirectory(resourcesPath);
	}
}

class MultipleProxyServlet extends org.mitre.dsmiley.httpproxy.ProxyServlet {
	private static final long serialVersionUID = 1L;
	@Override
	protected HttpClient createHttpClient() {
		return httpClient;
	}
	
	final Map<String, URI> proxyMappings;
	final  HttpClient httpClient;
	/**
	 *
	 * key 패턴, value URI
	 *
	 * @param proxyMappings
	 */
	public MultipleProxyServlet(Map<String, URI> proxyMappings, HttpClient httpClient) {
		this.proxyMappings = proxyMappings;
		this.httpClient=httpClient;
		super.hopByHopHeaders.addHeader(new BasicHeader("Cache-Control", null));
		super.hopByHopHeaders.addHeader(new BasicHeader("Pragma", null));
		super.hopByHopHeaders.addHeader(new BasicHeader("Expires", null));
		super.hopByHopHeaders.addHeader(new BasicHeader("Vary", null));
		super.hopByHopHeaders.addHeader(new BasicHeader("Server", null));
	}

	@Override
	protected void initTarget() throws ServletException {
		if (proxyMappings == null || proxyMappings.isEmpty()) {
			throw new ServletException(P_TARGET_URI + " is required.");
		}
	}

	@Override
	protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException {
		// CORS 헤더 추가 (지도 이미지 캡처용) 
		//servletResponse.setHeader("Access-Control-Allow-Origin", "*");
		//servletResponse.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");

		String requestURI = servletRequest.getRequestURI();
		Set<Map.Entry<String, URI>> entrySet = proxyMappings.entrySet();
		for (Map.Entry<String, URI> entry : entrySet) {
			String pattern = entry.getKey();
			if (pattern.endsWith("/*")) {
				String prefix = pattern.substring(0, pattern.length() - 1);
				if (requestURI.startsWith(prefix)) {
					servletResponse.setHeader("Cache-Control", "max-age=3600, public");
					servletRequest.setAttribute(ATTR_TARGET_URI, entry.getValue().getPath());
					servletRequest.setAttribute(ATTR_TARGET_HOST, org.apache.http.client.utils.URIUtils.extractHost(entry.getValue()));
					super.service(servletRequest, servletResponse);
					break;
				}
			} else if (requestURI.equals(pattern)) {
				servletResponse.setHeader("Cache-Control", "max-age=3600, public");
				servletRequest.setAttribute(ATTR_TARGET_URI, entry.getValue().getPath());
				servletRequest.setAttribute(ATTR_TARGET_HOST, org.apache.http.client.utils.URIUtils.extractHost(entry.getValue()));
				super.service(servletRequest, servletResponse);
				break;
			}
		}
	}

}


