package com.eactive.eai.rms.common.interceptor;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

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

import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import com.eactive.eai.rms.common.acl.menu.RequiresWritePermission;
import com.eactive.eai.rms.common.login.SessionManager;
import com.eactive.eai.rms.common.spring.LocaleMessage;
import com.eactive.eai.rms.data.entity.man.menu.MenuService;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class AuthorizeInterceptor implements HandlerInterceptor {

	private final MenuService menuService;
	private final ObjectMapper objectMapper;
	private final LocaleMessage localeMessage;

	private static final String VIEW_SUFFIX = ".view";

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		// InterceptorSkipController 구현 컨트롤러는 권한 체크 스킵
		if (handler instanceof HandlerMethod) {
			Class<?> clazz = ((HandlerMethod) handler).getBeanType();
			if (ClassUtils.isAssignable(clazz, InterceptorSkipController.class)) {
				return true;
			}
		}

		// 요청 URI, 사용자 ID, 서비스 타입, cmd 추출
		String uri = request.getRequestURI();
		String userId = SessionManager.getUserId(request);
		String serviceType = request.getParameter("serviceType");
		String cmd = request.getParameter("cmd");

		boolean hasPermission = true;
		if (StringUtils.endsWithAny(uri, ".json", ".file", VIEW_SUFFIX)) {
			// .json 또는 .file인 경우 .view로 변경
			String viewUrl = uri.replaceAll("\\.(json|file)$", VIEW_SUFFIX);
			// 권한 조회
			String permission = menuService.getUrlAuth(userId, viewUrl, serviceType);
			boolean requiresWritePermission = checkIfRequiresWritePermission(handler);
			// 권한 체크
			hasPermission = hasPermission(cmd, permission, requiresWritePermission);
		}

		// 권한이 없는 경우 처리
		if (!hasPermission) {
			handleAccessDenied(uri, request, response);
			return false;
		}

		// 이외의 URL은 허용
		return true;
	}

	/**
	 * 메서드가 @RequiresWritePermission 어노테이션을 가지고 있는지 확인
	 */
	private boolean checkIfRequiresWritePermission(Object handler) {
		if (handler instanceof HandlerMethod) {
			RequiresWritePermission annotation = ((HandlerMethod) handler)
					.getMethodAnnotation(RequiresWritePermission.class);
			return annotation != null;
		}
		return false;
	}

	/**
	 * 권한을 체크
	 */
	private boolean hasPermission(String cmd, String permission, boolean requiresWritePermission) {
		boolean hasWritePermission = "W".equals(permission);

		// 쓰기 권한이 있는 경우 모든 기능 사용 가능
		if (hasWritePermission) {
			return true;
		}

		// 쓰기 권한이 필요한 메서드인데 권한이 없는 경우 요청 차단
		if (requiresWritePermission) {
			return false;
		}

		// 읽기 권한이 있고, 쓰기 권한이 필요하지 않은 경우 요청 허용
		return "R".equals(permission) && !isWritePermissionCommand(cmd);
	}

	/**
	 * 주어진 cmd가 쓰기 권한이 필요한 cmd인지 확인
	 */
	private boolean isWritePermissionCommand(String cmd) {
		if (cmd == null) {
			return false;
		}

		return cmd.startsWith("INSERT")
				|| cmd.startsWith("UPDATE")
				|| cmd.startsWith("DELETE")
				|| cmd.startsWith("TRANSACTION")
				|| cmd.startsWith("UPLOAD")
				|| cmd.startsWith("INITIALIZE")
				|| cmd.startsWith("CLONE");
	}

	/**
	 * 권한이 없는 경우 처리
	 * @param request 
	 * 
	 */
	private void handleAccessDenied(String uri, HttpServletRequest request, HttpServletResponse response) throws IOException {
		response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
		
		if (StringUtils.endsWithAny(uri, ".json", ".file")) {
			
	        response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
			
			Map<String, String> resultMap = new HashMap<>();
			resultMap.put("status", "fail");
			resultMap.put("errorMsg", localeMessage.getMessage("access.denied"));

			String jsonResponse = objectMapper.writeValueAsString(resultMap);

			response.getWriter().write(jsonResponse);
			response.getWriter().flush();
			response.getWriter().close();
		} else if (uri.endsWith(VIEW_SUFFIX)) {
			// JSP 페이지로 리디렉션
			response.sendRedirect(request.getContextPath() + "/common/errors/access.jsp");
		}
	}
}
