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

import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.naming.Context;
import org.apache.commons.lang3.StringUtils;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.View;

import com.eactive.eai.common.seed.Seed;
import com.eactive.ext.djb.DamoManager;
import com.eactive.eai.data.converter.LocalDateTimeFormatters;
import com.eactive.eai.rms.common.context.MonitoringContext;
import com.eactive.eai.rms.common.datasource.DataSourceType;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.common.interceptor.InterceptorSkipController;
import com.eactive.eai.rms.common.logging.UserAccessLogger;
import com.eactive.eai.rms.common.util.IpUtil;
import com.eactive.eai.rms.common.spring.LocaleMessage;
import com.eactive.eai.rms.common.util.CommonConstants;
import com.eactive.eai.rms.data.entity.man.monitoringProperty.service.MonitoringPropertyService;
import com.eactive.apim.portal.user.entity.UserInfo;
import com.eactive.eai.rms.data.entity.man.user.UserInfoService;
import com.eactive.eai.rms.data.entity.man.user.UserLoginHistory;
import com.eactive.eai.rms.data.entity.man.user.UserLoginHistoryService;
import com.eactive.eai.rms.data.entity.man.user.UserRole;
import com.eactive.eai.rms.data.entity.man.user.UserRoleService;
import com.eactive.eai.rms.data.entity.man.user.UserServiceTypeService;
import com.eactive.eai.rms.ext.djb.smsauth.SmsAuthService;

@Controller
public class MainController implements InterceptorSkipController {

	private static final Logger logger = Logger.getLogger(MainController.class);
	private static final String LOG_CATEGORY_LOGIN = "Login";
	private static final String RESULT_MSG = "resultMsg";
	private static final String USER_NOT_FOUND_KEY_1 = "login.useridnotfound1";
	private static final String USER_NOT_FOUND_KEY_2 = "login.useridnotfound2";

	private static final String REDIRECT_LOGIN_URL = "redirect:/emergency.jsp";
	private static final String REDIRECT_CHANGE_PASSWORD_URL = "redirect:/emergency.jsp";

	private final LocaleMessage localeMessage;
	private final MonitoringContext monitoringContext;

	private final UserInfoService userInfoService;
	private final UserRoleService userRoleService;
	private final UserServiceTypeService userServiceTypeService;
	private final LoginService loginService;
	private final MonitoringPropertyService monitoringPropertyService;
	private final UserLoginHistoryService userLoginHistoryService;
	private final SmsAuthService smsAuthService;

	@Autowired
	public MainController(LocaleMessage localeMessage,
						  MonitoringContext monitoringContext,
						  UserInfoService userInfoService,
						  LoginService loginService,
						  UserRoleService userRoleService,
						  UserServiceTypeService userServiceTypeService,
						  MonitoringPropertyService monitoringPropertyService,
						  UserLoginHistoryService userLoginHistoryService,
						  SmsAuthService smsAuthService) {
		this.localeMessage = localeMessage;
		this.monitoringContext = monitoringContext;
		this.userInfoService = userInfoService;
		this.loginService = loginService;
		this.userRoleService = userRoleService;
		this.userServiceTypeService = userServiceTypeService;
		this.monitoringPropertyService = monitoringPropertyService;
		this.userLoginHistoryService = userLoginHistoryService;
		this.smsAuthService = smsAuthService;
	}

	@RequestMapping(value = "/loginForm.do")
	public String loginForm(HttpServletRequest request, Model model) {
		boolean fullScreen = request.getParameter("fullScreen") != null;
		boolean emergencyMode = request.getParameter("emergency") != null;
	    model.addAttribute("fullScreen", fullScreen);
	    model.addAttribute("emergencyMode", emergencyMode);

	    // 세션에 비상모드 저장 (SMS 인증 우회용)
	    HttpSession session = request.getSession();
	    session.setAttribute("emergencyMode", emergencyMode);

	    if (emergencyMode) {
	    	logger.info("비상 로그인 모드 활성화 - SSO/SMS 인증 우회");
	    }

		return "/common/screen/emergency";
	}

	@RequestMapping("/login.do")
	public String preMain(HttpServletRequest request, LoginVo dto) {

		if (dto == null) {
			dto = new LoginVo();
		}

		HttpSession session = request.getSession();

		try {
			initializeLoginAndSetDashboardImage(session);

			UserInfo userInfo = authenticateAndVerifyUser(request, session,dto);

			if (userInfo == null) { return REDIRECT_LOGIN_URL; }

			return loadUserInfoAndSetupSession(request, session, dto, userInfo);
		} catch (Exception e) {
			return handleExceptionsAndRedirect(request, session, e);
		}

	}

	/**
	 * AJAX 로그인 엔드포인트 (SMS 2차 인증 대응)
	 */
	@RequestMapping("/login-ajax.do")
	@ResponseBody
	public LoginResponseDto preMainAjax(HttpServletRequest request, LoginVo dto) {
		if (dto == null) {
			dto = new LoginVo();
		}

		HttpSession session = request.getSession();

		try {
			initializeLoginAndSetDashboardImage(session);

			// 1. 비밀번호 인증
			UserInfo userInfo = authenticateAndVerifyUser(request, session, dto);

			if (userInfo == null) {
				String errorMsg = (String) session.getAttribute(RESULT_MSG);
				session.removeAttribute(RESULT_MSG);
				return LoginResponseDto.builder()
						.success(false)
						.errorMessage(errorMsg != null ? errorMsg : "로그인에 실패했습니다.")
						.build();
			}

			// 2. 비상모드 체크 - SMS 인증 우회
			Boolean emergencyMode = (Boolean) session.getAttribute("emergencyMode");
			if (Boolean.TRUE.equals(emergencyMode)) {
				logger.info("비상 로그인 모드 - SMS 인증 우회 - userId: " + userInfo.getUserid());
				session.removeAttribute("emergencyMode"); // 사용 후 제거
				// 바로 로그인 완료로 이동
				String result = loadUserInfoAndSetupSession(request, session, dto, userInfo);
				String contextPath = request.getContextPath();
				String redirectUrl = contextPath + "/main.do";
				if (result != null && result.startsWith("redirect:")) {
					String path = result.substring("redirect:".length());
					if (!path.startsWith(contextPath)) {
						redirectUrl = contextPath + path;
					} else {
						redirectUrl = path;
					}
				}
				String serviceType = request.getParameter("serviceType");
				if (serviceType != null && !redirectUrl.contains("serviceType")) {
					redirectUrl += (redirectUrl.contains("?") ? "&" : "?") + "serviceType=" + serviceType;
				}
				return LoginResponseDto.builder()
						.success(true)
						.smsAuthRequired(false)
						.redirectUrl(redirectUrl)
						.build();
			}
			

			// 2.1 초기 비밀번호인 경우, 비밀번호 변경해야 함
			String subfix = monitoringContext.getStringProperty(MonitoringContext.RMS_PASSWORD_INIT_SUBFIX, "@!");
			if (userInfo.getPassword().equals(DamoManager.getInstance().hash(DamoManager.SHA256, userInfo.getUserid()+subfix))
					|| userInfo.getPassword().equals(DamoManager.getInstance().hash(DamoManager.SHA256, userInfo.getUserid()))) {
				return LoginResponseDto.builder()
						.success(false)
						.changePassword(true)
						.errorMessage("비밀번호 변경후 사용가능합니다.")
						.build();
			}
			

			// 3. SMS 인증 필요 여부 체크
			SmsAuthService smsAuthService = getSmsAuthService();
			if (smsAuthService != null && smsAuthService.isEnabled()) {
				// SMS 인증 활성화 상태
				if (!smsAuthService.hasValidPhoneNumber(userInfo)) {
					// 휴대폰번호 없는 사용자
					return LoginResponseDto.builder()
							.success(false)
							.errorMessage("휴대폰번호가 등록되어 있지 않아 로그인할 수 없습니다. 관리자에게 문의하세요.")
							.build();
				}

				// SMS 인증번호 생성 및 발송
				String authCode = smsAuthService.generateAuthCode();
				boolean sent = smsAuthService.sendAuthCode(userInfo, authCode);

				if (!sent) {
					return LoginResponseDto.builder()
							.success(false)
							.errorMessage("인증번호 발송에 실패했습니다. 잠시 후 다시 시도해주세요.")
							.build();
				}

				// 세션에 SMS 인증 정보 저장
				smsAuthService.setupAuthSession(session, userInfo, dto, authCode);

				// serviceType 세션에 저장
				String serviceType = request.getParameter("serviceType");
				if (serviceType != null) {
					session.setAttribute("serviceType", serviceType);
				}

				return LoginResponseDto.builder()
						.success(true)
						.smsAuthRequired(true)
						.maskedPhone(smsAuthService.maskPhoneNumber(userInfo.getCphnno()))
						.expiresAt(smsAuthService.getExpiresAt(session))
						.resendableAt(smsAuthService.getResendableAt(session))
						.build();
			}

			// 3. SMS 인증 비활성화 또는 서비스 없음 - 바로 로그인 완료
			String result = loadUserInfoAndSetupSession(request, session, dto, userInfo);
			String contextPath = request.getContextPath();
			String redirectUrl = contextPath + "/main.do";
			if (result != null && result.startsWith("redirect:")) {
				String path = result.substring("redirect:".length());
				if (!path.startsWith(contextPath)) {
					redirectUrl = contextPath + path;
				} else {
					redirectUrl = path;
				}
			}

			// serviceType 파라미터 추가
			String serviceType = request.getParameter("serviceType");
			if (serviceType != null && !redirectUrl.contains("serviceType")) {
				redirectUrl += (redirectUrl.contains("?") ? "&" : "?") + "serviceType=" + serviceType;
			}

			return LoginResponseDto.builder()
					.success(true)
					.smsAuthRequired(false)
					.redirectUrl(redirectUrl)
					.build();

		} catch (Exception e) {
			logger.error("AJAX 로그인 처리 중 오류", e);
			return LoginResponseDto.builder()
					.success(false)
					.errorMessage("로그인 처리 중 오류가 발생했습니다.")
					.build();
		}
	}

	private SmsAuthService getSmsAuthService() {
		return smsAuthService;
	}

	/**
	 * SSO 로그인용 세션 설정 (SsoController에서 호출)
	 * 비밀번호 인증 없이 UserInfo만으로 로그인 처리
	 */
	public String setupSsoLoginSession(HttpServletRequest request, UserInfo userInfo) {
		HttpSession session = request.getSession(true);

		try {
			initializeLoginAndSetDashboardImage(session);

			LoginVo dto = new LoginVo();
			dto.setUserId(userInfo.getUserid());

			session.setAttribute("userId", userInfo.getUserid());
			session.setAttribute("dualLogin", "N");
			SessionManager.forceRegisterUserSession(request, dto);
			// roleList가 설정된 후 loadUserInfoAndSetupSession 내부에서 세션에 저장됨

			String result = loadUserInfoAndSetupSession(request, session, dto, userInfo);

			// SSO 로그인 시 기본 serviceType 파라미터 추가 (외부 링크 직접 접근 대응)
			if ("redirect:/main.do".equals(result)) {
				DataSourceType defaultType = DataSourceTypeManager.getDynamicDataSourceTypes().get(0);
				return "redirect:/main.do?serviceType=" + defaultType.getName();
			}
			return result;
		} catch (Exception e) {
			return handleExceptionsAndRedirect(request, session, e);
		}
	}

	// 초기 설정 및 대시보드 이미지 설정
	private void initializeLoginAndSetDashboardImage(HttpSession session) {

		String newTitle = monitoringContext.getStringProperty(
				MonitoringContext.NEW_DASHBOARD_TITLE,
				"topLogo.gif");
		session.setAttribute(MonitoringContext.NEW_DASHBOARD_TITLE, newTitle);
	}

	// 사용자 인증 및 검증
	private UserInfo authenticateAndVerifyUser(HttpServletRequest request,
											   HttpSession session, LoginVo dto) throws Exception {

		if (dto.getUserId() == null || dto.getPassword() == null) {
			return null;
		}
				
		// UserInfo 체크.
		Optional<UserInfo> userInfoOptional = userInfoService
				.findById(dto.getUserId());

		if (!userInfoOptional.isPresent()) {
			setLoginFailure(request, session, USER_NOT_FOUND_KEY_1,
					USER_NOT_FOUND_KEY_2);
			return null;
		}
		UserInfo userInfo = userInfoOptional.get();
		
		//logger.info("사용자 계정 상태: " + userInfo.getStatus());
				
		if ( userInfo.getStatus() != null && "2".equals(userInfo.getStatus()) ) {
			setLoginFailure(request, session, "login.checkstatus1",
					"login.checkstatus2");
			return null;			
		}
		
		//logger.info("사용자 ip: " + userInfo.getAllowip());
		//logger.info("사용자 계정 role: " + userInfo.getRoleidnfiname());
		//logger.info("사용자 계정 admin? " + userInfo.getRoleidnfiname().contains("admin"));
		
		/* craft
		boolean adminYn = userInfo.getRoleidnfiname().contains("admin");
		
		if(adminYn) {
			
			if(userInfo.getAllowip() == null || (userInfo.getLastloginip() != userInfo.getAllowip())) {				
				setLoginFailure(request, session, "login.checkallowip1",
						"login.checkallowip2");
				return null;				
			}
			
		}
		*/

		String loginMode = System.getProperty("login.mode");
		if("db".equals(loginMode) || "DB".equals(loginMode)) {
			/* 기존로직 - LDAP 사용 x */
			if (!userInfo.getPassword().equals(DamoManager.getInstance().hash(DamoManager.SHA256, dto.getPassword()))) {
				// password 다름.
				setLoginFailure(request, session, "login.loginfail1",
						"login.loginfail2");
				return null;
			}
		} else {
			
			String password = dto.getPassword();
			
			password = password.replace("&#35;","#");
			password = password.replace("&#38;","&");
			
			/* LDAP */
			//if (!authenticate(userInfo.getUserid(), dto.getPassword())) {
			if (!authenticate(userInfo.getUserid(), password)) {
				// password 다름.
				setLoginFailure(request, session, "login.loginfail1",
						"login.loginfail2");
				return null;
			}
		}
								
		session = request.getSession(true);
		session.setAttribute("userId", dto.getUserId());
		
		session.setAttribute("dualLogin", "N");
		
		boolean isDualLogin = monitoringContext.getBooleanProperty(MonitoringContext.RMS_DUAL_LOGIN_ENABLED, true);
		
		if (!isDualLogin) {
			// 이중 로그인 확인
		    boolean isNewLogin = SessionManager.registerUserSession(request, dto);
			
		    if (!isNewLogin) {
		        
		    	// 1.
		        SessionManager.forceRegisterUserSession(request, dto);	        
		        session.setAttribute("dualLogin", "Y"); 
		        
		        // 또는
		        // 2. 이중 로그인 에러 표시
		        // model.addAttribute("error", "다른 기기에서 이미 로그인 중입니다.");
		        // return "/";
		    }
		}
	    
	    // 세션에 로그인 정보 저장
	    request.getSession().setAttribute(CommonConstants.LOGIN, dto);		
				
		UserLoginHistory userLoinHistory = new UserLoginHistory();
		userLoinHistory.setUserId(userInfo.getUserid());
		userLoinHistory.setLoginIp(userInfo.getLastloginip());
		userLoinHistory.setLoginDttm(LocalDateTime.now());

		userLoginHistoryService.save(userLoinHistory);

		return userInfo;
	}

	// LDAP 
	public boolean authenticate(String userId, String password) {
		String LDAP_URL = monitoringPropertyService.getPrpty2ValById("Monitoring", "ldap.domain");
		String NETBIOS = monitoringPropertyService.getPrpty2ValById("Monitoring", "ldap.netbios");

		Hashtable<String, String> env = new Hashtable<>();
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		env.put(Context.PROVIDER_URL, "ldap://" + LDAP_URL);
		env.put(Context.SECURITY_AUTHENTICATION, "simple");
		env.put(Context.SECURITY_PRINCIPAL, NETBIOS + "\\" + userId);
		env.put(Context.SECURITY_CREDENTIALS, password);

		try {
			DirContext context = new InitialDirContext(env);
			context.close();
			return true;
		}catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	/**
	 * 사용자 정보 로드 및 세션 설정
	 * public으로 변경하여 SmsAuthController에서 호출 가능
	 */
	public String loadUserInfoAndSetupSession(HttpServletRequest request,
											  HttpSession session, LoginVo dto, UserInfo userInfo)
			throws Exception {

		// LDAP 사용시 주석
//		if (loginService.needChangePassword(userInfo)) {
//			setLoginFailure(request, session, "login.pwdnotchanged1",
//									"login.pwdnotchanged2");
//			return REDIRECT_CHANGE_PASSWORD_URL;
//		}

		// 사용자 정보 업데이트
		updateUserInfo(request, userInfo);

		// 사용자 마지막 로그인 정보 로깅 및 세션 설정
		logger.debug("사용자 아이디 : " + userInfo.getUserid());
		logger.info("LastLoginYms : " + userInfo.getLastloginyms()
				+ ", LastLoginIp : "
				+ userInfo.getLastloginip());
		session.setAttribute("LastLoginYms",
				userInfo.getLastloginyms().format(
						LocalDateTimeFormatters.FORMATTER_YYYYMMDDHHMMSS_14));
		session.setAttribute("LastLoginIp", userInfo.getLastloginip());

		// 사용자 역할 설정
		setUserRolesInSession(dto, userInfo);

		if (!processServiceType(request, dto)) { return REDIRECT_LOGIN_URL; }

		session.setAttribute(CommonConstants.LOGIN, dto);
		return "redirect:/main.do";
	}

	// 사용자 최신 로그인 정보 업데이트
	private void updateUserInfo(HttpServletRequest request, UserInfo userInfo) {
		String lastLoginIp = IpUtil.getClientIp(request);
		userInfo.setLastloginyms(LocalDateTime.now());
		userInfo.setLastloginip(lastLoginIp);
		userInfoService.save(userInfo);
	}

	// 사용자 역할 정보 세션 설정
	private void setUserRolesInSession(LoginVo dto, UserInfo userInfo) {
		List<UserRole> userRoles = userRoleService
				.findByUserId(userInfo.getUserid());
		List<String> roles = new ArrayList<>();

		for (UserRole userRole : userRoles) {
			roles.add(userRole.getId().getRoleId());
		}

		dto.setDepartment(userInfo.getDvsnname());
		dto.setUserName(userInfo.getUsername());
		dto.setRoleList(roles);
	}

	// 사용자 서비스타입 검증, 확인
	private boolean processServiceType(HttpServletRequest request,
									   LoginVo dto) {
		String serviceTypeValidationYn = monitoringContext.getStringProperty(
				MonitoringContext.USER_SERVICETYPE_VALIDATION,
				"N");

		if (serviceTypeValidationYn.equals("Y")) {
			String useServiceTypes = userServiceTypeService
					.selectLoginUseServiceTypes(
							dto.getUserId());

			if (serviceTypeValidation(useServiceTypes, request)) {
				dto.setUseServiceTypes(useServiceTypes);
				return true;
			} else {
				return false;
			}

		} else {
			dto.setUseServiceTypes(CommonConstants.USER_SERVICETYPE_NO_CHECK);
			return true;
		}

	}

	// 예외 처리 및 리다이렉션 결정
	private String handleExceptionsAndRedirect(HttpServletRequest request,
											   HttpSession session, Exception e) {
		logger.error(localeMessage.getString("login.grantfail1") + " User="
				+ SessionManager.getUserId(request), e);
		session.setAttribute(MonitoringContext.RMS_LOGON_ERR_TITLE,
				monitoringContext.getStringProperty(
						MonitoringContext.RMS_LOGON_ERR_TITLE,
						"EAI Monitoring System"));
		session.setAttribute(MonitoringContext.RMS_LOGON_ERR_BODY,
				localeMessage.getString("login.grantfail2")
						+ "<BR><BR>"
						+ e.getMessage());
		return "redirect:/common/errors/errorLogon.jsp";
	}

	// 로그인 실패 시 로그 기록, 세션에 실패 메시지 설정
	private void setLoginFailure(HttpServletRequest request,
								 HttpSession session, String logKey, String msgKey) {
		UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
				localeMessage.getString(logKey));
		session.setAttribute(RESULT_MSG, localeMessage.getString(msgKey));
	}

	// 서비스 타입 검증
	private boolean serviceTypeValidation(String useServiceTypes,
										  HttpServletRequest request) {
		HttpSession session = request.getSession();

		if (useServiceTypes == null || useServiceTypes.isEmpty()) {
			setLoginFailure(request, session, "login.checkUseYnLog",
					"login.checkUseYn");
			return false;
		}

		String loginPageServiceType = request
				.getParameter("serviceType");

		if (loginPageServiceType != null && !loginPageServiceType.isEmpty()
				&& !useServiceTypes.contains(
				loginPageServiceType)) {
			setLoginFailure(request, session, "login.checkGrantLog",
					"login.checkGrant");
			return false;
		}

		String[] serviceTypes = useServiceTypes.split(",");

		for (String serviceType : serviceTypes) {
			DataSourceType dataSourceType = DataSourceTypeManager
					.getDataSourceType(serviceType);

			if (dataSourceType == null) {
				setLoginFailure(request, session, "login.checkServiceTypeLog",
						"login.checkServiceType");
				return false;
			}

		}

		return true;
	}

	@RequestMapping("/dashboard.do")
	public String goDashboard(HttpServletRequest request, String service) {

		if (logger.isDebugEnabled()) {
			logger.debug("CURRENT SERVICE TYPE => " + service);
			logger.debug("redirectPage =>"
					+ request.getSession().getAttribute(
					"redirectPage"));
		}

		return "redirect:/common/jsp/redirect.jsp";
	}

	@RequestMapping("/main.do")
	public String goMain(HttpServletRequest request, String service,
						 String mainPage, String menuId, ModelMap model)
			throws Exception {

		// 대시보드 상세화면으로부터 특정화면을 지정하여 Open 하는 경우.
		mainPage = mainPage == null ? "" : mainPage;
		menuId = menuId == null ? "" : menuId;
		model.addAttribute("mainPage", mainPage);
		model.addAttribute("menuId", menuId);
		model.addAttribute("autoLogoutTimeout",
				monitoringContext.getIntProperty(MonitoringContext.RMS_AUTO_LOGOUT_TIMEOUT, 60));

		if (logger.isDebugEnabled()) {
			logger.debug("CURRENT SERVICE TYPE : " + service);
			logger.debug("mainPage=" + mainPage);
		}

		return "/common/screen/main";
	}

	@RequestMapping("/changeOwner.do")
	public View changeOwner(HttpServletRequest request, String service,
							String serviceType) {

		if (logger.isDebugEnabled()) {
			logger.debug("CHANGE OWNER : " + service);
		}

		request.getSession().setAttribute(CommonConstants.SERVICE_TYPE_KEY,
				serviceType);

		return new View() {
			@Override
			public void render(@SuppressWarnings("rawtypes") Map model,
							   HttpServletRequest request,
							   HttpServletResponse response)
					throws Exception {
				PrintWriter out = new PrintWriter(response.getOutputStream());
				out.println();
				out.flush();
			}

			@Override
			public String getContentType() {
				return "text/html";
			}
		};
	}

	@RequestMapping("/rms/logout.do")
	public String rmsLogout(HttpServletRequest request,
							HttpServletResponse response) {

		UserAccessLogger.log(request, "Logout", "S",
				localeMessage.getString("login.logout"));

		// 보안모듈 로그아웃 ( LoginServlet?method=logout )
		// 이 브라우저가 닫히지 않는 오류 가 있어서 logout 페이지 로 직접 redirect 한다.

		LoginVo loginVo = SessionManager.getLoginVo(request);
	    
	    if (loginVo != null) {
	        // 로그인 세션 제거
	        //SessionManager.removeUserSession(loginVo.getUserId());
	        SessionManager.removeUserSessionBySessionId(request.getSession().getId());	//2026.04.29 이중로그인 로그아웃 버그 수정
	        
	        request.getSession().removeAttribute(CommonConstants.LOGIN);
	    	
	        // 세션 무효화
	        request.getSession().invalidate();
	    }
		
		// 로그아웃 화면에서 별도의 로직처리 없어서 바로 login 페이지로 이동한다.
		return "redirect:/";
	}

	@RequestMapping("/checkSession.do")
	public View checkSession(HttpServletRequest request,
							 HttpServletResponse response) {
		
		UserAccessLogger.log(request, "checkSession", "S", "");

		// 한글처리 관련
		response.setContentType("text/xml;charset=utf-8");
		response.setHeader("Cache-Control", "no-cache");
				
		try {
			ServletOutputStream out = response.getOutputStream();

			String url = "F";
			LoginVo dto = SessionManager.getLoginVo(request);
			
			if (dto != null && dto.getUserId() != null) {
				url = "T";
			}

			out.print(url);
		} catch (IOException e) {
			logger.error("checkSession error", e);
		}

		return null;
		
	}

	/**
	 * 신대시보드에 표현할 거래건수 데이터를 어느 DB 에서 읽어 올것인가 하는 정보
	 *
	 * @return
	 */
	public String getTargetService() {
		String local = "";

		try {
			InetAddress addr = InetAddress.getLocalHost();
			local = addr.getHostAddress();
		} catch (UnknownHostException e) {
			logger.error(e);
		}

		// 타겟서비스에 해당하는 DB 의 거래현황 데이터를 읽어서 해당DB 의 RM11 테이블에 집계데이터를
		// 추가한다.
		// ALL : 온라인용 ( 전체 서비스 )
		// OUTSIDE : 테스트용 ( ZAUA - 192.168.175.82 )
		// COMMON : 테스트용 ( ZAPA - 192.168.175.54 )
		// 현재 사용되고 있지 않은곳이 내부(ZADA) 이므로 일단 ALL 데이터는 ZADA 로 WRITE 한다.
		String targetSvc = "ALL";
		String target = monitoringContext.getStringProperty(
				MonitoringContext.RMS_TRADE_COUNT_TARGET, "");
		String[] arrTmp = target.split(",");

		if (arrTmp != null && arrTmp.length > 0) {

			for (int i = 0; i < arrTmp.length; i++) {
				String[] arrTmp2 = arrTmp[i].split("=");

				if (arrTmp2 != null && arrTmp2.length == 2
						&& local.equals(arrTmp2[0])) {
					targetSvc = arrTmp2[1];
					break;
				}

			}

		}

		return targetSvc;
	}

	@RequestMapping("/changePassword.do")
	public String changePassword(HttpServletRequest request,
								 String changePassword, String confirmPassword,
								 String resetUserId, String resetPassword) {
		HttpSession session = request.getSession();
		// AJAX 로그인 인증 후 초기비밀번호 변경 경로에서 세션에 부분 로그인 상태가 남아
		// root emergency.jsp가 main.do로 잘못 리다이렉트하는 것을 방지
		session.removeAttribute(CommonConstants.LOGIN);

		try {

			// 로그인 User ID 체크.
			Optional<UserInfo> userInfoOptional = userInfoService
					.findById(resetUserId);

			if (!userInfoOptional.isPresent()) {
				UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
						localeMessage
								.getString(USER_NOT_FOUND_KEY_1));
				session.setAttribute(RESULT_MSG, localeMessage
						.getString(USER_NOT_FOUND_KEY_1));
				return REDIRECT_LOGIN_URL;
			}

			UserInfo userInfo = userInfoOptional.get();

			// 1. check 변경비밀번호와 확인비밀번호 비교
			if (!changePassword.equals(confirmPassword)) {
				UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
						localeMessage
								.getString("login.pwdmismatch1"));
				session.setAttribute(RESULT_MSG, localeMessage
						.getString("login.pwdmismatch2"));
				return REDIRECT_CHANGE_PASSWORD_URL;
			}

			// 2. 비밀번호 Validation
			if (!isPasswordValid(changePassword)) {
				UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
						localeMessage
								.getString("login.pwdmismatch3"));
				session.setAttribute(RESULT_MSG, localeMessage
						.getString("login.pwdmismatch4"));
				return REDIRECT_CHANGE_PASSWORD_URL;
			}
			
			// 3. 비밀번호 체크
			if (!userInfo.getPassword().equals(DamoManager.getInstance().hash(DamoManager.SHA256,resetPassword))
					|| !userInfo.getUserid().equals(resetUserId)) {
				UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "F",
						localeMessage
								.getString("login.pwdmismatch5"));
				session.setAttribute(RESULT_MSG, localeMessage
						.getString("login.pwdmismatch6"));
				return REDIRECT_CHANGE_PASSWORD_URL;
			}

			// 4. 비밀번호 변경
			userInfo.setPassword(DamoManager.getInstance().hash(DamoManager.SHA256,changePassword));
			userInfoService.save(userInfo);

			UserAccessLogger.log(request, LOG_CATEGORY_LOGIN, "S", localeMessage
					.getString("login.pwdchanged"));
			session.setAttribute(RESULT_MSG, localeMessage
					.getString("login.pwdchanged"));
			logger.debug("\n\n\n\n===================" + localeMessage
					.getString("login.pwdchanged"));
			return REDIRECT_LOGIN_URL;

		} catch (Exception e) {
			logger.error("changePassword Error ", e);
			return "redirect:/common/errors/errorLogon.jsp";
		}

	}

	private boolean isPasswordValid(String password) {

		return password != null && password.getBytes().length == password
				.length() && password.getBytes().length >= 7
				&& isCombination(password)
				&& !isRepeatPassword(password, 5)
				&& !isSerialPassword(password, 5);
	}

	private boolean isRepeatPassword(String password, int count) {
		char preData = " ".charAt(0);
		int loopcount = 1;

		for (int i = 0; i < password.length(); i++) {

			if (preData == password.charAt(i)) {
				loopcount++;
			} else {
				loopcount = 1;
			}

			preData = password.charAt(i);

			if (loopcount == count) { return true; }

		}

		return false;
	}

	private boolean isSerialPassword(String password, int count) {
		int preData = 0;
		int serialcount = 1;

		for (int i = 0; i < password.length(); i++) {

			if (preData + 1 == password.charAt(i)) {
				serialcount++;
			} else {
				serialcount = 1;
			}

			preData = password.charAt(i);

			if (serialcount == count) { return true; }

		}

		return false;
	}

	private boolean isCombination(String password) {
		int combicount = 0;
		// 숫자포함여부
		String pattern = "[0-9]+";
		Pattern p = Pattern.compile(pattern);
		Matcher m = p.matcher(password);

		if (m.find()) {
			combicount++;
		}

		// 문자 포함여부
		pattern = "[a-zA-Z]+";
		p = Pattern.compile(pattern);
		m = p.matcher(password);

		if (m.find()) {
			combicount++;
		}

		// 특수문자
		pattern = "[^0-9a-zA-Z]+";
		p = Pattern.compile(pattern);
		m = p.matcher(password);

		if (m.find()) {
			combicount++;
		}

		// combicount가 2 이상이면 true, 그렇지 않으면 false 반환
		return combicount >= 2;

	}
	
}
