package kr.co.uplus.ez.api.login;

import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import kr.co.uplus.ez.api.comm.CommService;
import kr.co.uplus.ez.api.comm.dto.Menu;
import kr.co.uplus.ez.api.login.dto.*;
import kr.co.uplus.ez.common.data.ApiResponseCode;
import kr.co.uplus.ez.common.data.Const;
import kr.co.uplus.ez.common.jwt.JwtService;
import kr.co.uplus.ez.config.SecurityConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping(value = "/api/v1/bo/login")
public class LoginController {
	private static final Logger logger = LoggerFactory.getLogger(LoginController.class);

	@Autowired
	private AuthenticationManager authManager;
	@Autowired
	private LoginSuccessHandler loginSuccessHandler;
	@Autowired
	private LoginFailureHandler loginFailureHandler;
	@Autowired
	private LoginService loginService;
	@Autowired
	private CommService commService;
	
	@Autowired
	private JwtService jwtSvc;

	// 2차인증 제외 ID
	@Value("#{'${authentication.without.id}'.split(',')}")
	private List<String> authenticationWithoutId;


	/**
	 * date : 2022. 5. 3.
	 * auth : ckr
	 * desc : HUBEZ_BO_API_2001 - 로그인.
	 * @param loginReqDto
	 * @return LoginResDto
	 * @
	 */
	@ApiOperation(value = "login", notes = "로그인")
	@ApiResponses({
		@ApiResponse(code = HttpServletResponse.SC_OK, message = "SUCESS")
	})
	@PostMapping("login")
	public LoginResDto login(@RequestBody LoginReqDto loginReqDto) {
		return loginService.firstLoginChk(loginReqDto);
    }


	/**
	 * 로그인 전에 요청했던 URL 반환
	 */
	private String getReturnUrl(HttpServletRequest request, HttpServletResponse response) {
		RequestCache requestCache = new HttpSessionRequestCache();
		SavedRequest savedRequest = requestCache.getRequest(request, response);
		if (savedRequest == null) {
			AuthUser user = (AuthUser) request.getAttribute(Const.KEY_LOAD_USER);
			return SecurityConfig.LOGIN_SUCC_URL;
		}
		return savedRequest.getRedirectUrl();
	}

	/**
	 * date : 2022. 5. 17.
	 * auth : ckr
	 * desc : HUBEZ_BO_API_2002 - 인증번호 요청.
	 * @param authNumReqDto
	 * @return AuthNumResDto
	 * @
	 */
	@ApiOperation(value = "authNum", notes = "인증번호 요청")
	@ApiResponses({
		@ApiResponse(code = HttpServletResponse.SC_OK, message = "SUCESS")
	})
	@PostMapping("authNum")
	@ResponseBody
	public AuthNumResDto authNum(@RequestBody AuthNumReqDto authNumReqDto) {
		return loginService.sendAuthNum(authNumReqDto);
	}

	/**
	 * date : 2022. 5. 17.
	 * auth : ckr
	 * desc : HUBEZ_BO_API_2003 - 인증번호 확인.
	 * 		  2차인증번호 인증 Spring security
	 *
	 * @param authNumDto
	 * @param request
	 * @param response
	 * @return ChkAuthNumResDto
	 */
	@ApiOperation(value = "confirmNum", notes = "인증번호 확인")
	@ApiResponses({
		@ApiResponse(code = HttpServletResponse.SC_OK, message = "SUCESS")
	})
	@PostMapping("confirmNum")
	@ResponseBody
	public ChkAuthNumResDto confirmNum(@RequestBody ChkAuthNumReqDto authNumDto, HttpServletRequest request, HttpServletResponse response) {
		AuthUser user = new AuthUser();
		user.setOprtrId(authNumDto.getOprtrId());
		user.setInputPwd(authNumDto.getOprtrPw());

		// 시큐리티 인증
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
        Authentication authentication = null;

        try {
        	authentication = authManager.authenticate(token);
        }
        catch (AuthenticationException e) {
        	request.setAttribute(SecurityConfig.LOGIN_ID_PARAM, user.getOprtrId());
        	ApiResponseCode resultCode = loginFailureHandler.process(request, response, e);
        	return new ChkAuthNumResDto(resultCode);
        }

        // 2차인증 패스..
        for (int i = 0; i < authenticationWithoutId.size(); i++) {
			if (authNumDto.getOprtrId().contains(authenticationWithoutId.get(i).trim())) {
				loginSuccessHandler.process(request, response, authentication);

				// Auth 토큰 생성
				String authToken = jwtSvc.generatePrivateToken(authentication);
				// Create RefreshToken 
				String refreshToken = jwtSvc.refreshToken(authentication);
				
				String nextUrl = getReturnUrl(request, response);
				logger.debug("login SUCCESS - nextUrl = [{}]", nextUrl);
				AuthUser authUser = loginService.getUser(authentication.getName());
				String auth = authUser.getAutCd();
				Menu menuData = commService.getByRoleMenu(auth);

				ArrayList<Menu> menu = (ArrayList<Menu>) menuData.getChildren();
				logger.info(menu.get(0).toString());
				Menu rootMenu = menu.get(0);
				String rootUrl = rootMenu.getChildren().get(0).getMenuUrl();

				ArrayList<String> menuUrls = new ArrayList<String>();
				for(int j=0; j< menu.size(); j++) {
					Menu parentMenu = menu.get(j);
					if(parentMenu.getChildren().size() > 0) {
						ArrayList<Menu> childrenMenu = (ArrayList<Menu>)parentMenu.getChildren();
						for(int k=0; k< childrenMenu.size(); k++) {
							Menu childMenu = childrenMenu.get(k);
							String menuUrl = childMenu.getMenuUrl();
							menuUrls.add(menuUrl);
						}
					}
				}
				
				LoginRes loginRes = new LoginRes(rootUrl);
				loginRes.setUserId(authUser.getOprtrId());
				loginRes.setUserNm(authUser.getOprtrNm());
				loginRes.setMenuUrls(menuUrls);
				loginRes.setAuthToken(authToken);
				loginRes.setRefreshToken(refreshToken);
				
				ChkAuthNumResDto result = new ChkAuthNumResDto(ApiResponseCode.SUCCESS, loginRes);
				return result;
			}
        }

        ApiResponseCode rCode = loginService.confirmNum(authNumDto);

		if(rCode.equals(ApiResponseCode.SUCCESS)) {

			// 2차인증후 시큐리티 성공핸들러
			rCode = loginSuccessHandler.process(request, response, authentication);
			
			// loginToken
			String authToken = jwtSvc.generatePrivateToken(authentication);
			// Create RefreshToken
			String refreshToken = jwtSvc.refreshToken(authentication);
			
			String nextUrl = getReturnUrl(request, response);
			logger.debug("login SUCCESS - nextUrl = [{}]", nextUrl);
			AuthUser authUser = loginService.getUser(authentication.getName());
			String auth = authUser.getAutCd();
			Menu menuData = commService.getByRoleMenu(auth);			
			
			ArrayList<Menu> menu = (ArrayList<Menu>) menuData.getChildren();
			logger.info(menu.get(0).toString());
			Menu rootMenu = menu.get(0);
			String rootUrl = rootMenu.getChildren().get(0).getMenuUrl();
			
			// 메뉴 url data 생성
			ArrayList<String> menuUrls = new ArrayList<String>();
			for(int j=0; j< menu.size(); j++) {
				Menu parentMenu = menu.get(j);
				if(parentMenu.getChildren().size() > 0) {
					ArrayList<Menu> childrenMenu = (ArrayList<Menu>)parentMenu.getChildren();
					for(int k=0; k< childrenMenu.size(); k++) {
						Menu childMenu = childrenMenu.get(k);
						String menuUrl = childMenu.getMenuUrl();
						menuUrls.add(menuUrl);
					}
				}
			}
			
			LoginRes loginRes = new LoginRes(rootUrl);
			loginRes.setUserId(authUser.getOprtrId());
			loginRes.setUserNm(authUser.getOprtrNm());
			loginRes.setAuthToken(authToken);
			loginRes.setRefreshToken(refreshToken);
			
			loginRes.setMenuUrls(menuUrls);
			
			ChkAuthNumResDto result = new ChkAuthNumResDto(rCode, loginRes);
			return result;
		}else {
			ChkAuthNumResDto result = new ChkAuthNumResDto(rCode);
			return result;
		}

	}

	/**
	 * HUBEZ_BO_API_2004 - 비밀번호 초기화 요청.
	 *
	 * @param resetPasswordReqDto
	 * @return ResetPasswordResDto
	 * @
	 */
	@ApiOperation(value = "resetPassword", notes = "비밀번호 초기화 요청")
	@ApiResponses({
		@ApiResponse(code = HttpServletResponse.SC_OK, message = "SUCESS")
	})
	@PostMapping("resetPassword")
	@ResponseBody
	public ResetPasswordResDto resetPassword(@RequestBody ResetPasswordReqDto resetPasswordReqDto) {
		return loginService.resetPassword(resetPasswordReqDto);
	}

	/**
	 * HUBEZ_BO_API_2005 - 비밀번호 변경.
	 *
	 * @param updatePasswordReqDto
	 * @return UpdatePasswordResDto
	 * @
	 */
	@ApiOperation(value = "updatePassword", notes = "비밀번호 변경")
	@ApiResponses({
		@ApiResponse(code = HttpServletResponse.SC_OK, message = "SUCESS")
	})
	@PostMapping("updatePassword")
	@ResponseBody
	public UpdatePasswordResDto updatePassword(@RequestBody UpdatePasswordReqDto updatePasswordReqDto) {
		return loginService.updatePassword(updatePasswordReqDto);
	}

	/**
	 * HUBEZ_BO_API_2006 - 로그아웃.
	 *
	 * @param logoutReqDto
	 * @param request
	 * @param response
	 * @return LogoutResDto
	 * @
	 */
	@ApiOperation(value = "logout", notes = "로그아웃")
	@ApiResponses({
		@ApiResponse(code = HttpServletResponse.SC_OK, message = "SUCESS")
	})
	@PostMapping("logout")
	@ResponseBody
	public LogoutResDto logout(@RequestBody LogoutReqDto logoutReqDto, HttpServletRequest request, HttpServletResponse response) {
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		if (auth != null){
			new SecurityContextLogoutHandler().logout(request, response, auth);
		}
//		jwtSvc.destroyPrivateToken(request, response);

		return new LogoutResDto();
	}
	
	/**
	 * date : 2022. 8. 17.
	 * auth : ckr
	 * desc : 2차 인증 페이지 진입시 accessToken 체크
	 * @param loginCheckDto
	 * @param request
	 * @param response
	 * @return LoginResDto
	 */
	@PostMapping("loginCheck")
	@ResponseBody
	public LoginResDto loginCheck(@RequestBody LoginReqDto loginCheckDto, HttpServletRequest request, HttpServletResponse response) {
		
		return loginService.loginCheck(loginCheckDto);
	}
}
