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

import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import io.jsonwebtoken.Claims;
import kr.co.uplus.ez.api.login.dto.AuthNum;
import kr.co.uplus.ez.api.login.dto.AuthNumReqDto;
import kr.co.uplus.ez.api.login.dto.AuthNumResDto;
import kr.co.uplus.ez.api.login.dto.AuthUser;
import kr.co.uplus.ez.api.login.dto.ChkAuthNumReqDto;
import kr.co.uplus.ez.api.login.dto.LoginReqDto;
import kr.co.uplus.ez.api.login.dto.LoginRes;
import kr.co.uplus.ez.api.login.dto.LoginResDto;
import kr.co.uplus.ez.api.login.dto.ResetPasswordReqDto;
import kr.co.uplus.ez.api.login.dto.ResetPasswordResDto;
import kr.co.uplus.ez.api.login.dto.SendMsgDto;
import kr.co.uplus.ez.api.login.dto.UpdatePasswordReqDto;
import kr.co.uplus.ez.api.login.dto.UpdatePasswordResDto;
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.common.utils.DateUtils;
import kr.co.uplus.ez.common.utils.EncryptionUtil;
import kr.co.uplus.ez.common.utils.TextUtils;
import kr.co.uplus.ez.config.SecurityConfig;

@Service
public class LoginService {

	private static final Logger logger = LoggerFactory.getLogger(LoginService.class);

	@Autowired
	@Qualifier("sqlSessionTemplateDb1")
	private SqlSessionTemplate sqlSessionMaster;

	@Autowired
	@Qualifier("sqlSessionTemplateDb2")
	private SqlSessionTemplate sqlSessionSlave;

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

	@Value("${sendMsg.tableNm}")
	private String sendMsgTableName;

	@Value("${authentication.searchTime.min-30: 30}")
	private String searchTime30M;
	
	@Value("${authentication.searchTime.min-01: 1}")
	private String searchTime01M;	
	
	@Value("${authentication.cntLimit: 10}")
	private int cntLimit;
	
	@Autowired
	private JwtService jwtSvc;
	
	/**
	 * 1차 로그인 인증
	 *
	 * @param loginInfo
	 * @return LoginResDto
	 */
	public LoginResDto firstLoginChk(LoginReqDto loginInfo) {

		LoginMapper loginMapper = sqlSessionMaster.getMapper(LoginMapper.class);

		String userId = loginInfo.getOprtrId();
		AuthUser dbUserParam = new AuthUser();
		dbUserParam.setOprtrId(userId);

		AuthUser user = loginMapper.getUser(dbUserParam);

		if (user == null) {
			// 사용자 여부
			return new LoginResDto(ApiResponseCode.CE_ID_NOT_FOUND);
		}

		if (user.getLoginFailCnt() >= Const.MAX_LOGIN_FAIL) {
			// 5회 로그인 실패 체크
			return new LoginResDto(ApiResponseCode.CE_TO_LOCK);
		}

		if (user.getSttusCd().equals(Const.CODE_VALUE_02)) {
			// 사용자 상태 체크
			return new LoginResDto(ApiResponseCode.SS_NOT_USE);
		}

		// 비밀번호 체크
		String userPw = loginInfo.getOprtrPw();
		String encPwd = EncryptionUtil.getCustomSHA512(userId, userPw);
		if (!user.getPwd().equals(encPwd)) {
			// 로그인 실패 카운트 증가
			loginMapper.increaseFailCount(userId);

			return new LoginResDto(ApiResponseCode.CE_ID_PWD);
		} else {

			// 비밀번호 만료 일시 체크.
			if(StringUtils.equals(Const.COMM_YES, user.getPwChangeCheck())) {
				return new LoginResDto(ApiResponseCode.CE_PWD_EXPIRE);
			}

			// 로그인 실패카운트 초기화
			loginMapper.setLoginInfo(user.getOprtrId());
		}
		
		// 로그인 임시 비밀번호 여부
		String tmpPwdYn = user.getTmpPwdYn();
		if(tmpPwdYn.equals(Const.COMM_YES)) {
			return new LoginResDto(ApiResponseCode.LOGIN_UPDATE_PWD, new LoginRes(SecurityConfig.LOGIN_UPDATE_PWD_URL));
		}
		
		// access 토큰 생성
		LoginRes loginRes = new LoginRes();
		
		String accessToken = jwtSvc.accessToken(user);
		loginRes.setAccessToken(accessToken);
		loginRes.setNextUrl(SecurityConfig.VUE_URL_ARRY[0].toString());		
		return new LoginResDto(ApiResponseCode.SUCCESS, loginRes);
	}

	/**
	 * 인증번호 요청
	 *
	 * @param reqAuthNumDto
	 * @return AuthNumResDto
	 */
	public AuthNumResDto sendAuthNum(AuthNumReqDto reqAuthNumDto) {

		LoginMapper loginMapper = sqlSessionMaster.getMapper(LoginMapper.class);

		// 1차 로그인 체크
		if (reqAuthNumDto.getIsLogin() != null && reqAuthNumDto.getIsLogin()) {
			// return 1차로그인 인증 실패 코드
		}

		String userId = reqAuthNumDto.getOprtrId(); // 인증 받고자 하는 userId


        // 2차인증 패스..
        for (int i = 0; i < authenticationWithoutId.size(); i++) {
			if (userId.contains(authenticationWithoutId.get(i).trim())) {
				return new AuthNumResDto();
			}
        }

		AuthUser dbUserParam = new AuthUser();
		dbUserParam.setOprtrId(userId);

		AuthUser user = loginMapper.getUser(dbUserParam);

		// 인증 요청 계정의 정보 체크(ID, HP)
		if (user != null) {
			if (!user.getOprtrId().equals(userId)) {
				return new AuthNumResDto(ApiResponseCode.CE_ID_HP);
			}
			if (!user.getHpNo().equals(reqAuthNumDto.getHpNo())) {
				return new AuthNumResDto(ApiResponseCode.CE_ID_HP);
			}
		} else {
			return new AuthNumResDto(ApiResponseCode.CE_ID_NOT_FOUND);
		}

		// 2차 인증 실패 카운트 체크
		int autchrFailCnt = user.getAuthchrFailCnt();
		if (autchrFailCnt >= Const.MAX_AUTHNUM_FAIL) {
			return new AuthNumResDto(ApiResponseCode.CE_AUTHNUM_LOCK);
		}
		
		/*------------------------ 인증요청 임계 체크 2022.08.01 --------------------------------------- */
		// 인증 요청 시간에서 30분 데이터 가져오기
		Date nowDate = new Date();
		String now = DateUtils.dateToStr(nowDate, "yyyyMMddHHmmss");
		
		AuthNum authParam = new AuthNum();
		authParam.setRegDt(now);
		authParam.setSttusCd(Const.AUTH_STTUS_CD_03);
		authParam.setHpNo(user.getHpNo());
		authParam.setSearchTime(searchTime30M);
		
		List<AuthNum> authNumList = loginMapper.getAuthNumList(authParam);
		
		if(authNumList != null && authNumList.size() > 0) { // 30분 이내에 lock 상태가 존재함
			return new AuthNumResDto(ApiResponseCode.CE_AUTHNUM_STAT_LOCK);			
		}
		
		authParam = new AuthNum();
		authParam.setRegDt(now);
		authParam.setSttusCd(Const.AUTH_STTUS_CD_01);
		authParam.setHpNo(user.getHpNo());
		authParam.setSearchTime(searchTime01M);
		authNumList = null;
		
		authNumList = loginMapper.getAuthNumList(authParam);
		
		String authNum = TextUtils.randNumStr(6);
		
		if(authNumList != null) {
			if(authNumList.size() >= cntLimit) {
				// 발송 요청 건수가 1분에 10개 이상
				
				// 발행한 인증번호 DB에 저장
				AuthNum anum = new AuthNum();
				anum.setAuthTpCd(Const.AUTH_TP_CD);
				anum.setSttusCd(Const.AUTH_STTUS_CD_03);
				anum.setHpNo(user.getHpNo());
				anum.setChrVal(authNum);
				anum.setRegId(user.getOprtrId());
				loginMapper.addAuthNum(anum);
				
				return new AuthNumResDto(ApiResponseCode.CE_AUTHNUM_STAT_LOCK);
			}
		}		

		// 발행한 인증번호 DB에 저장
		AuthNum anum = new AuthNum();
		anum.setAuthTpCd(Const.AUTH_TP_CD);
		anum.setSttusCd(Const.AUTH_STTUS_CD_01);
		anum.setHpNo(user.getHpNo());
		anum.setChrVal(authNum);
		anum.setRegId(user.getOprtrId());
		loginMapper.addAuthNum(anum);

		// 문자발송
		String clientKey = DateUtils.date2strYMDHMS() + "AD" + RandomStringUtils.randomAlphanumeric(8);
		SendMsgDto sendMsgDto = new SendMsgDto();
		sendMsgDto.setClientKey(clientKey);
		sendMsgDto.setMsg("[LG U+ 메시지허브이지]\n"
				+ "로그인 2차인증 인증번호는 " + authNum + " 입니다.");
		sendMsgDto.setPhone(user.getHpNo());
		sendMsgDto.setTableName(sendMsgTableName);
		loginMapper.insertSendMsg(sendMsgDto);

		return new AuthNumResDto();

	}

	/**
	 * 인증 번호 확인
	 *
	 * @param authNumDto
	 * @return ApiResponseCode
	 */
	public ApiResponseCode confirmNum(ChkAuthNumReqDto authNumDto) {

		LoginMapper loginMapper = sqlSessionMaster.getMapper(LoginMapper.class);

		AuthNum anum = new AuthNum();
		anum.setOprtrId(authNumDto.getOprtrId());
		anum.setAuthTpCd(Const.AUTH_TP_CD);
		anum.setSttusCd(Const.AUTH_STTUS_CD_01);		// 사용중
		anum.setHpNo(authNumDto.getHpNo());

		AuthNum curr = loginMapper.getAuthNum(anum);

		if (curr == null) {
			return ApiResponseCode.CE_WRONG_AUTHNUM;
		}

		if (curr.getAuthchrFailCnt() >= Const.MAX_AUTHNUM_FAIL) {
			return ApiResponseCode.CE_AUTHNUM_LOCK;
		}


		if (!curr.getChrVal().equals(authNumDto.getChrVal())) {
			// 실패 카운트 증가
			String oprtrId = curr.getOprtrId();
			loginMapper.increaseAuthFailCnt(oprtrId);

			return ApiResponseCode.CE_WRONG_AUTHNUM;
		}

		AuthNum authNum = new AuthNum();
		authNum.setOprtrId(curr.getOprtrId());
		authNum.setAuthchrFailCnt(0);
		loginMapper.setAuthUserInfo(authNum);

		// 인증요청
		authNum = new AuthNum();
		authNum.setSeqNo(curr.getSeqNo());
		authNum.setSttusCd(Const.AUTH_STTUS_CD_02);
		loginMapper.setAuthNum(authNum);

		return ApiResponseCode.SUCCESS;
	}

	/**
	 * 비밀번호 초기화.
	 *
	 * @param resetPasswordReqDto
	 * @return ResetPasswordResDto
	 */
	public ResetPasswordResDto resetPassword(ResetPasswordReqDto resetPasswordReqDto) {

		LoginMapper loginMapper = sqlSessionMaster.getMapper(LoginMapper.class);

		AuthUser dbUserParam = new AuthUser();
		dbUserParam.setOprtrId(resetPasswordReqDto.getUserId());

		// 1. 사용자 ID 조회
		AuthUser user = loginMapper.getUser(dbUserParam);

		if (user == null) {
			// 사용자 여부
			return new ResetPasswordResDto(ApiResponseCode.CE_ID_NOT_FOUND);
		}

		// 2. 임시비밀번호 생성
		String randomPw = RandomStringUtils.randomAlphanumeric(10);

		// 3. update EZ_ADM_USER (비밀번호, 임시비밀번호여부, 비밀번호변경일시)
		AuthUser authUser = new AuthUser();
		authUser.setOprtrId(resetPasswordReqDto.getUserId());
		authUser.setTmpPwdYn(Const.COMM_YES);
		authUser.setPwd(EncryptionUtil.getCustomSHA512(resetPasswordReqDto.getUserId(),  randomPw));
		authUser.setPwdChgDt(Const.COMM_YES);
		authUser.setAuthchrFailCnt(0);
		authUser.setLoginFailCnt(0);
		authUser.setPwdOld(user.getPwd());

		int reslut = loginMapper.updateAdmUser(authUser);

		if(reslut < 1) {
			// 업데이트 실패.
			return new ResetPasswordResDto(ApiResponseCode.CM_DB_QUERY_ERR);
		}

		// 4. 임시 비밀번호 문자발송
		String clientKey = DateUtils.date2strYMDHMS() + "AD" + RandomStringUtils.randomAlphanumeric(8);
		SendMsgDto sendMsgDto = new SendMsgDto();
		sendMsgDto.setClientKey(clientKey);
		sendMsgDto.setMsg("[LG U+] 메시지허브이지 임시 비밀번호\n"+ randomPw + "\n※ 로그인후 비밀번호를 변경해 주세요.");
		
		sendMsgDto.setPhone(user.getHpNo());
		sendMsgDto.setTableName(sendMsgTableName);
		loginMapper.insertSendMsg(sendMsgDto);

		return new ResetPasswordResDto();
	}

	/**
	 * 비밀번호 변경.
	 *
	 * @param updatePasswordReqDto
	 * @return UpdatePasswordResDto
	 */
	public UpdatePasswordResDto updatePassword(UpdatePasswordReqDto updatePasswordReqDto) {

		LoginMapper loginMapper = sqlSessionMaster.getMapper(LoginMapper.class);

		AuthUser dbUserParam = new AuthUser();
		dbUserParam.setOprtrId(updatePasswordReqDto.getUserId());
		dbUserParam.setPwd(EncryptionUtil.getCustomSHA512(updatePasswordReqDto.getUserId(), updatePasswordReqDto.getOldPw()));
		// 1. 사용자 ID 조회
		AuthUser user = loginMapper.getUser(dbUserParam);

		if (user == null) {
			// 사용자 여부
			return new UpdatePasswordResDto(ApiResponseCode.CE_ID_NOT_FOUND);
		}

		String newPwd =  EncryptionUtil.getCustomSHA512(updatePasswordReqDto.getUserId(),  updatePasswordReqDto.getNewPw());
		String nowPwd = user.getPwd();
		
		// 현재 비밀번호랑 동일 한지 체크
		if(nowPwd.equals(newPwd)) {
			return new UpdatePasswordResDto(ApiResponseCode.CE_FAILED_TO_PW_CHANGE);
		}
		
		// 이전 패스워드랑 동일 한지 체크
		if(user.getPwdOld() != null) {
			String oldPwd = user.getPwdOld();
			if(oldPwd.equals(newPwd)) {
				return new UpdatePasswordResDto(ApiResponseCode.CE_FAILED_TO_PW_CHANGE);
			}
		}
		
		// 2. 비밀번호 변경 - update EZ_ADM_USER (비밀번호, 비밀번호변경일시)
		AuthUser authUser = new AuthUser();
		authUser.setOprtrId(updatePasswordReqDto.getUserId());
		authUser.setPwd(newPwd);
		authUser.setPwdChgDt(Const.COMM_YES);
		authUser.setAuthchrFailCnt(0);
		authUser.setTmpPwdYn(Const.COMM_NO);
		authUser.setPwdOld(nowPwd);

		int reslut = loginMapper.updateAdmUser(authUser);

		if(reslut < 1) {
			// 업데이트 실패.
			return new UpdatePasswordResDto(ApiResponseCode.CM_DB_QUERY_ERR);
		}

		return new UpdatePasswordResDto();
	}

	/**
	 * 로그인 성공시 초기화.
	 *
	 * @param userId
	 */
	public void setLoginInfo(String userId) {
		LoginMapper authUserMapper = sqlSessionMaster.getMapper(LoginMapper.class);
		authUserMapper.setLoginInfo(userId);
	}

	/**
	 * 로그인 실패 카운트 증가.
	 *
	 * @param userId
	 * @return FailCnt
	 */
	public int increaseFailCount(String userId) {
		LoginMapper authUserMapper = sqlSessionMaster.getMapper(LoginMapper.class);
		authUserMapper.increaseFailCount(userId);
		AuthUser user = authUserMapper.getByUsername(userId);
		return user.getLoginFailCnt();
	}

	/**
	 * 사용자 상태 변경.
	 *
	 * @param userId
	 * @param status
	 */
	public void setUserStatus(String userId, String status) {
		LoginMapper authUserMapper = sqlSessionMaster.getMapper(LoginMapper.class);
		AuthUser user = new AuthUser();
		user.setOprtrId(userId);
		user.setSttusCd(status);
		authUserMapper.setUserStatus(user);
	}

	/**
	 * 사용자 정보 조회.
	 *
	 * @param userId
	 * @return AuthUser
	 */
	public AuthUser getUser(String userId) {
		LoginMapper authUserMapper = sqlSessionSlave.getMapper(LoginMapper.class);
		AuthUser dbUserParam = new AuthUser();
		dbUserParam.setOprtrId(userId);
		return authUserMapper.getUser(dbUserParam);
	}
	
	/**
	 * access 토큰 확인
	 * 
	 * @param loginCheckDto
	 * @return LoginResDto
	 */
	@SuppressWarnings("unchecked")
	public LoginResDto loginCheck(LoginReqDto loginCheckDto){
		String accessTokenStr = "";		
		try {
			accessTokenStr = loginCheckDto.getAccessToken();
			// validate check
			if(accessTokenStr == null || accessTokenStr.equals("")) {
				// 에러 코드 처리
				return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR);
			}
			String oprtrId = loginCheckDto.getOprtrId();
			if(oprtrId == null || oprtrId.equals("")) {
				// 에러 코드 처리
				return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR);
			}
			
			Claims claims = jwtSvc.parseToken(accessTokenStr);			
			String tokenId = claims.get("jti").toString();
			
			if(!oprtrId.equals(tokenId)) {
				// 에러 코드 처리
				return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR);
			}
			
			// 토큰 파싱
			Map<String, Object> principal = (Map<String, Object>) claims.get("principal");
			String parsingOprtrId = principal.get("oprtrId").toString();
			String parsingOprtrPwd = principal.get("pwd").toString();
			
			if(!parsingOprtrId.equals(tokenId)) {
				// 에러 코드 처리
				return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR);
			}
			
			// get user info 
			LoginMapper loginMapper = sqlSessionMaster.getMapper(LoginMapper.class);
			String userId = parsingOprtrId; 
			AuthUser dbUserParam = new AuthUser();
			dbUserParam.setOprtrId(userId);
			AuthUser user = loginMapper.getUser(dbUserParam);
			
			if(user == null) {
				// 사용자 조회 결과 없음
				return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR);
			}
			
			if(!parsingOprtrPwd.equals(user.getPwd())) {
				// 에러 코드 처리
				return new LoginResDto(ApiResponseCode.CE_AUTH_ERROR);
			}
			
			
		}catch(Exception e) {
			logger.info(e.getMessage());
			return null;
		}
		
		LoginRes loginRes = new LoginRes();
		loginRes.setAccessToken(accessTokenStr);
		
		return new LoginResDto(ApiResponseCode.SUCCESS, loginRes);
	}

}
