/*
 * Copyright 2011- 2020 SDCIT Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package sgc.portal.com.sym.user.service;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sgc.portal.SgcPortalAttributes;
import sgc.portal.SgcPortalProperties;
import sgc.portal.base.exception.BusinessException;
import sgc.portal.base.util.EasyHashMap;
import sgc.portal.base.util.SessionUtil;
import sgc.portal.base.util.StringUtil;
import sgc.portal.com.sym.user.AuthenticationException;
import sgc.portal.com.sym.user.model.Login;
import sgc.portal.com.sym.user.model.LoginAs;
import sgc.portal.com.sym.user.model.PasswordChange;
import sgc.portal.com.sym.user.model.UserDetails;
import sgc.portal.com.sym.user.persistence.LoginMapper;
import sgc.portal.security.PasswordEncryptionService;
import sgc.portal.security.SessionListener;
import sgc.portal.security.model.Authority;
import sgc.portal.security.model.AuthorityCriterion;
import sgc.portal.security.model.SystemAuthority;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Base64.Decoder;

/**
 * 로그인 서비스 구현.
 *
 * @author SDCIT
 * @version 1.0.0
 * @since 2020. 7. 5.
 */
@Slf4j
@Service
@Transactional
public class LoginService {
	@Autowired
	private SgcPortalProperties props;

	@Autowired
	private PasswordEncryptionService passwordEncryptionService;

	/** 매퍼 선언 */
	@Autowired
	private LoginMapper loginMapper;

	@Autowired
	private MultipartProperties mp;

	@Autowired
	private Environment environment;

	/**
	 * 로그인 인증을 위한 Skeleton code로 향후 실제 업무에 맞게 수정 필요. yongsang 2020.07.26
	 *
	 * @param login
	 *            the login
	 * @return the user details
	 */
	public UserDetails authenticate(Login login) {
		return loginMapper.authenticate(login);
	}

	/**
	 * session Create
	 *
	 * @param userDetails
	 *            the user details
	 * @param request
	 *            the request
	 */
	public boolean createSession(UserDetails userDetails, HttpServletRequest request) {
		boolean alreadyUsed = false;
		HttpSession session = request.getSession();

		/* 기접속자 세션 삭제. */
		if (!props.getSecurity().isDuplicateLoginUsed()
				&& SessionListener.getInstance().isUsing(userDetails.getLginId())) {
			alreadyUsed = true;
			SessionListener.getInstance().removeSession(userDetails.getLginId());
		}
		SessionListener.getInstance().setSession(session, userDetails.getLginId());

		// 사용자 접속 IP
		userDetails.setConnIp(StringUtil.getConnIp(request));
		userDetails.setConnBrowser(StringUtil.getConnBrowser(request));
		// 로그인/로그아웃 이력테이블 등록
		systemLogIn(userDetails);
		// 비밀번호 오류 초기화
		passwordFailReset(userDetails);
		// 세션 생셩
		SessionUtil.setAttribute(SgcPortalAttributes.AUTHENTICATION, userDetails);
		return alreadyUsed;
	}

	/**
	 * 로그인 처리
	 * 
	 * @param login
	 *            로그인 시도 정보
	 * @param request
	 * @return 로그인 사용자 정보 리턴
	 */
	public UserDetails login(Login login, HttpServletRequest request) {
		login.setScrtNo(decodeStr(login.getScrtNo()));

		if (StringUtils.isEmpty(login.getLginId()) || StringUtils.isEmpty(login.getScrtNo())) {
			UserDetails userDetails = new UserDetails();
			userDetails.setUserNm("IDPASSINVALID");
			return userDetails;
		}

    	String[] activeProfiles = environment.getActiveProfiles();
    	
		if(activeProfiles[0].equals("local")) {
			login.setScrtNo(loginMapper.insaSelectPassword(login));
		} else {
			String encryptedPwd = passwordEncryptionService.encryptPasswordWithSalt(login.getScrtNo());
			login.setScrtNo(encryptedPwd);
		}

		if (selectPasswordFailCount(login) > 0) {
			UserDetails userDetails = new UserDetails();
			userDetails.setUserNm("PASSFAIL");
			return userDetails;
		}
		UserDetails userDetails = authenticate(login);

		if (userDetails != null) {
			if(!activeProfiles[0].equals("local")) {
				if ("Y".equals(selectPasswordExpireCheck(login))) {
					userDetails.setUserNm("PASSEXPI");
					return userDetails;
				}
			}
			userDetails.setAlreadyUsed(createSession(userDetails, request));
		} else {
			updateFailCount(login);
			UserDetails userDetails2 = new UserDetails();
			userDetails2.setUserNm("PASSDIFF");
			return userDetails2;// throw new AuthenticationException("exception.authentication.invalid");
		}

		userDetails.setAllowedExtensions(props.getStorage().getAllowedExtensions());
		userDetails.setImgAllowedExtensions(props.getStorage().getImgAllowedExtensions());
		userDetails.setMaxFileSize(mp.getMaxFileSize().toBytes());

		for (String activeProfile : activeProfiles) {
			userDetails.setActiveProfile(activeProfile.toUpperCase());
		}
		userDetails.setAsMember(false);
		return userDetails;
	}

	/**
	 * 그룹웨어 포탈 로그인 처리
	 * 
	 * @param login
	 *            로그인 시도 정보
	 * @param request
	 * @return 로그인 사용자 정보 리턴
	 */
	public UserDetails loginInsa(Login login, HttpServletRequest request) {

		login.setScrtNo(loginMapper.insaSelectPassword(login));

		if (StringUtils.isEmpty(login.getLginId()) || StringUtils.isEmpty(login.getScrtNo())) {
			UserDetails userDetails = new UserDetails();
			userDetails.setUserNm("IDPASSINVALID");
			return userDetails;
		}

		//자동로그인시 패스워드 실패횟수 체크 제외
		//if (selectPasswordFailCount(login) > 0) {
		//	UserDetails userDetails = new UserDetails();
		//	userDetails.setUserNm("PASSFAIL");
		//	return userDetails;
		//}

		UserDetails userDetails = authenticate(login);

		if (userDetails != null) {
			//자동로그인시 패스워드 유효기간 체크 제외
			//if ("Y".equals(selectPasswordExpireCheck(login))) {
			//	userDetails.setUserNm("PASSEXPI");
			//	return userDetails;
			//}
			userDetails.setAlreadyUsed(createSession(userDetails, request));
		} else {
			updateFailCount(login);
			UserDetails userDetails2 = new UserDetails();
			userDetails2.setUserNm("PASSDIFF");
			return userDetails2;// throw new AuthenticationException("exception.authentication.invalid");
		}

		userDetails.setAllowedExtensions(props.getStorage().getAllowedExtensions());
		userDetails.setImgAllowedExtensions(props.getStorage().getImgAllowedExtensions());
		userDetails.setMaxFileSize(mp.getMaxFileSize().toBytes());

		String[] activeProfiles = environment.getActiveProfiles();
		for (String activeProfile : activeProfiles) {
			userDetails.setActiveProfile(activeProfile.toUpperCase());
		}
		userDetails.setAsMember(false);

		return userDetails;
	}

	/**
	 * 로그아웃 처리 한다.
	 * 
	 * @param session
	 */
	public void logOut(HttpSession session) {
		UserDetails userDetails = (UserDetails) session.getAttribute(SgcPortalAttributes.AUTHENTICATION);
		if (userDetails != null) {
			systemLogOut(userDetails);
		}
		SessionUtil.setAttribute(SgcPortalAttributes.AUTHENTICATION, null);
		session.invalidate();
	}

	/**
	 * System log in.
	 *
	 * @param userDetails
	 *            the user details
	 */
	public void systemLogIn(UserDetails userDetails) {
		if (userDetails != null) {
			if (userDetails.getCovendId() != null && !"".equals(userDetails.getCovendId())) {
				loginMapper.asSystemLogIn(userDetails);
			} else {
				loginMapper.systemLogIn(userDetails);
			}
		}
	}

	/**
	 * System log out.
	 *
	 * @param userDetails
	 *            the user details
	 */
	public void systemLogOut(UserDetails userDetails) {
		if (userDetails != null) {
			if (userDetails.getCovendId() != null && !"".equals(userDetails.getCovendId())) {
				loginMapper.asSystemLogOut(userDetails);
			} else {
				loginMapper.systemLogOut(userDetails);
			}
		}
	}

	/**
	 * Save pasword chang.
	 *
	 * @param passwordChange
	 *            the password chang
	 */
	public void savePasswordChange(PasswordChange passwordChange) {
		loginMapper.savePasswordChangeUpdate(passwordChange);
	}

	/**
	 * Select password fail count int.
	 *
	 * @param login
	 *            the login
	 * @return the int
	 */
	public int selectPasswordFailCount(Login login) {
		return loginMapper.selectPasswordFailCount(login);
	}

	/**
	 * Password fail reset.
	 *
	 * @param userDetails
	 *            the user details
	 */
	public void passwordFailReset(UserDetails userDetails) {
		loginMapper.passwordFailReset(userDetails);
	}

	/**
	 * Update fail count.
	 *
	 * @param login
	 *            the login
	 */
	public void updateFailCount(Login login) {
		loginMapper.updateFailCount(login);
	}

	/**
	 * Select password expire check string.
	 *
	 * @param login
	 *            the login
	 * @return the string
	 */
	public String selectPasswordExpireCheck(Login login) {
		return loginMapper.selectPasswordExpireCheck(login);
	}

	/**
	 * Exec password init check.
	 *
	 * @param login
	 *            the login
	 */
	// 비밀번호 초기화
	public void execPasswordInitCheck(Login login) {
		switch (login.getInitDiv()) {
		case "100":
			String hpNo = loginMapper.selectHpno(login);
			if (hpNo.equals("NONE")) {
				throw new BusinessException("해당 사용자의 휴대전화번호가 없습니다.");
			}
			String tpAuthNo = loginMapper.createTmpAproNo();
			login.setHpAutzNo(tpAuthNo);
			loginMapper.updateSmComUserForPasswordInit(login);
			break;
		case "200":
			int chkCnt = loginMapper.checkAuthNo(login);
			if (chkCnt <= 0) {
				throw new BusinessException("인증번호를 확인하십시요.");
			} else if (chkCnt > 1) {
				throw new BusinessException("비밀번호 초기화 작업을 다시 시도해 주시기 바랍니다.");
			}
			break;
		case "300":
			login.setScrtNo(decodeStr(login.getScrtNo()));
			String encryptedPwd = passwordEncryptionService.encryptPasswordWithSalt(login.getScrtNo());
			login.setScrtNo(encryptedPwd);
			int userChk = loginMapper.checkAuthCnt(login);
			if (userChk == 1) {
				loginMapper.updatePassword(login);
			}
			break;
		default:
			break;
		}
	}

	/**
	 * 임직원 암호 변경
	 * 
	 * @param passwordChange
	 * @return
	 */
	public UserDetails savePasswordChangePortal(PasswordChange passwordChange) {
		passwordChange.setPassword(this.decodeStr(passwordChange.getPassword()));
		passwordChange.setChangePassword(this.decodeStr(passwordChange.getChangePassword()));
		passwordChange.setReChangePassword(this.decodeStr(passwordChange.getReChangePassword()));
		Login login = new Login();
		String encryptedPwd = passwordEncryptionService.encryptPasswordWithSalt(passwordChange.getPassword());
		login.setCmpnCd(passwordChange.getCmpnCd()); 	// 회사코드
		login.setLginId(passwordChange.getLoginId()); 	// 로그인ID
		login.setScrtNo(encryptedPwd); 					// 암호화패스워드

		/*if (this.selectPasswordFailCount(login) > 0) {
			throw new BusinessException("비밀번호 입력 실패 횟수 초과. 관리자에게 연락하세요.");
			//throw new AuthenticationException("exception.authentication.invalidOver");
		}*/

		UserDetails userDetails = this.authenticate(login);

		if (userDetails != null) {
			this.passwordFailReset(userDetails);
			passwordChange.setPassword(encryptedPwd);
			String encryptedChPwd = passwordEncryptionService.encryptPasswordWithSalt(passwordChange.getChangePassword());
			passwordChange.setChangePassword(encryptedChPwd);
			this.savePasswordChange(passwordChange);
		} else {
			//this.updateFailCount(login);
			throw new BusinessException("사용자 정보가 일치 하지 않습니다.");
			//throw new AuthenticationException("exception.password.invalid");
		}
		return userDetails;
	}

	/**
	 * 협력업체 암호 변경
	 * 
	 * @param passwordChange
	 * @return
	 */
	public UserDetails savePasswordChangeAs(PasswordChange passwordChange) {
		passwordChange.setPassword(this.decodeStr(passwordChange.getPassword()));
		passwordChange.setChangePassword(this.decodeStr(passwordChange.getChangePassword()));
		passwordChange.setReChangePassword(this.decodeStr(passwordChange.getReChangePassword()));
		LoginAs login = new LoginAs();
		String encryptedPwd = passwordEncryptionService.encryptPasswordWithSalt(passwordChange.getPassword());
		login.setCmpnCd(passwordChange.getCmpnCd()); 	// 회사코드
		login.setLginId(passwordChange.getLoginId());	// 로그인ID
		login.setScrtNo(encryptedPwd);					// 암호화패스워드
		
		/*if (this.asSelectPasswordFailCount(login) > 0) {
			throw new BusinessException("비밀번호 입력 실패 횟수 초과. 관리자에게 연락하세요.");
			//throw new AuthenticationException("exception.authentication.invalidOver");
		}*/
		
		UserDetails userDetails = this.asAuthenticate(login);

		if (userDetails != null) {
			this.asPasswordFailReset(userDetails);
			passwordChange.setPassword(encryptedPwd);
			String encryptedChPwd = passwordEncryptionService.encryptPasswordWithSalt(passwordChange.getChangePassword());
			passwordChange.setChangePassword(encryptedChPwd);
			this.asSavePasswordChange(passwordChange);
		} else {
			//this.asUpdateFailCount(login);
			throw new BusinessException("사용자 정보가 일치 하지 않습니다.");
			//throw new AuthenticationException("exception.password.invalid");
		}
		return userDetails;
	}

	/**
	 * 협력업체 로그인 처리
	 * 
	 * @param login
	 *            로그인 시도 정보
	 * @param request
	 * @return 로그인 사용자 정보 리턴
	 */
	public UserDetails asLogin(LoginAs login, HttpServletRequest request) {
		login.setScrtNo(decodeStr(login.getScrtNo()));

		if (StringUtils.isEmpty(login.getLginId()) || StringUtils.isEmpty(login.getScrtNo())) {
			UserDetails userDetails = new UserDetails();
			userDetails.setUserNm("IDPASSINVALID");
			return userDetails;
		}

		String encryptedPwd = passwordEncryptionService.encryptPasswordWithSalt(login.getScrtNo());
		login.setScrtNo(encryptedPwd);

		if (asSelectPasswordFailCount(login) > 0) {
			UserDetails userDetails = new UserDetails();
			userDetails.setUserNm("PASSFAIL");
			return userDetails;
		}
		UserDetails userDetails = asAuthenticate(login);
		if (userDetails != null) {
			userDetails.setAuthorities(new ArrayList<Authority>());
			userDetails.setAuthorityCriteria(new ArrayList<AuthorityCriterion>());
			userDetails.setSystemAuthorities(new ArrayList<SystemAuthority>());
			userDetails.setAsMember(true);
			if ("Y".equals(asSelectPasswordExpireCheck(login))) {
				userDetails.setUserNm("PASSEXPI");
				return userDetails;
			}
			userDetails.setAlreadyUsed(createSession(userDetails, request));
		} else {
			asUpdateFailCount(login);
			UserDetails userDetails2 = new UserDetails();
			userDetails2.setUserNm("PASSDIFF");
			return userDetails2;// throw new AuthenticationException("exception.authentication.invalid");
		}

		userDetails.setAllowedExtensions(props.getStorage().getAllowedExtensions());
		userDetails.setImgAllowedExtensions(props.getStorage().getImgAllowedExtensions());
		userDetails.setMaxFileSize(mp.getMaxFileSize().toBytes());

		String[] activeProfiles = environment.getActiveProfiles();
		for (String activeProfile : activeProfiles) {
			userDetails.setActiveProfile(activeProfile.toUpperCase());
		}
		return userDetails;
	}

	/**
	 * 협력업체 로그인 인증을 위한 Skeleton code로 향후 실제 업무에 맞게 수정 필요. yongsang 2020.07.26
	 *
	 * @param login
	 *            the login
	 * @return the user details
	 */
	public UserDetails asAuthenticate(LoginAs login) {
		return loginMapper.asAuthenticate(login);
	}

	/**
	 * Save pasword chang.
	 *
	 * @param passwordChange
	 *            the password chang
	 */
	public void asSavePasswordChange(PasswordChange passwordChange) {
		loginMapper.asSavePasswordChangeUpdate(passwordChange);
	}

	/**
	 * 협력업체 Select password fail count int.
	 *
	 * @param login
	 *            the login
	 * @return the int
	 */
	public int asSelectPasswordFailCount(LoginAs login) {
		return loginMapper.asSelectPasswordFailCount(login);
	}

	/**
	 * 협력업체 Password fail reset.
	 *
	 * @param userDetails
	 *            the user details
	 */
	public void asPasswordFailReset(UserDetails userDetails) {
		loginMapper.asPasswordFailReset(userDetails);
	}

	/**
	 * 협력업체 Update fail count.
	 *
	 * @param login
	 *            the login
	 */
	public void asUpdateFailCount(LoginAs login) {
		loginMapper.asUpdateFailCount(login);
	}

	/**
	 * 협력업체 Select password expire check string.
	 *
	 * @param login
	 *            the login
	 * @return the string
	 */
	public String asSelectPasswordExpireCheck(LoginAs login) {
		return loginMapper.asSelectPasswordExpireCheck(login);
	}

	/**
	 * 협력 업체 아이디 찾기
	 * 
	 * @param login
	 * @return
	 */
	public LoginAs execAsIdFind(LoginAs login) {
		LoginAs findLogin = loginMapper.asIdFind(login);
		if (findLogin != null) {
			login.setUserId(findLogin.getUserId());
			login.setUserName(findLogin.getUserName());
		}
		return login;
	}

	/**
	 * 협력업체 비밀번호 초기화
	 * 
	 * @author 5364
	 * @param login
	 * @return
	 */
	public LoginAs execAsPasswordInit(LoginAs login) {
		int userChk = loginMapper.asCheckAuthCnt(login);
		if (userChk == 1) {
			String tmpPwd = "sgc1234!@"; // generateRandomPassword(5);
			String encryptedPwd = passwordEncryptionService.encryptPasswordWithSalt(tmpPwd);
			login.setScrtNo(encryptedPwd);
			loginMapper.asUpdatePassword(login);
			login.setScrtNo(tmpPwd);
		} else {
			login.setScrtNo("");
		}
		return login;
	}

	/**
	 * 협력업체 비밀번호 변경
	 * 
	 * @author 5364
	 * @param login
	 * @return
	 */
	public LoginAs execAsPasswordChange(LoginAs login) {
		login.setScrtNo(decodeStr(login.getScrtNo()));
		login.setUserId(login.getLginId());
		if (StringUtils.isEmpty(login.getLginId()) || StringUtils.isEmpty(login.getScrtNo())) {
			login.setScrtNo("");
			return login;
		}
		
		String encryptedPwd = passwordEncryptionService.encryptPasswordWithSalt(login.getScrtNo());
		login.setScrtNo(encryptedPwd);
		if (asSelectPasswordFailCount(login) > 0) {
			login.setScrtNo("");
			return login;
		} else {
		    UserDetails userDetails = asAuthenticate(login);
	        if (userDetails != null) {
    	        String tmpPwd = decodeStr(login.getTmpPasswd());
    			if (tmpPwd == null || "".equals(tmpPwd)) {
    				tmpPwd = "sgc1234!@";
    			}
    			encryptedPwd = passwordEncryptionService.encryptPasswordWithSalt(tmpPwd);
    			login.setScrtNo(encryptedPwd);
    			loginMapper.asUpdatePassword(login);
    			login.setScrtNo(tmpPwd);
	        } else {
	            throw new BusinessException("기존 암호가 일치하지 않습니다.");
	        }
		}
		return login;
	}

    /**
     * 암호 규칙 
     */
    public EasyHashMap<String, Object> selectComPasswordPolicyView() {
        return loginMapper.selectComPasswordPolicyView();
    }
	
	/**
	 * 랜덤 임시 비밀번호 생성
	 * 
	 * @author 5364
	 * @param len
	 * @return
	 */
	public String generateRandomPassword(int len) {
		// ASCII 범위 – 영숫자(0-9, a-z, A-Z)
		final String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
		SecureRandom random = new SecureRandom();
		StringBuilder sb = new StringBuilder();
		// 루프의 각 반복은 주어진 문자에서 무작위로 문자를 선택합니다.
		// ASCII 범위를 `StringBuilder` 인스턴스에 추가합니다.
		for (int i = 0; i < len; i++) {
			int randomIndex = random.nextInt(chars.length());
			sb.append(chars.charAt(randomIndex));
		}
		return sb.toString();
	}

	public String decodeStr(String str) {
		Decoder decoder = Base64.getDecoder();
		byte[] pw = decoder.decode(str);
		try {
			str = new String(pw, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			log.error(e.getMessage());
		}
		return str;
	}
}