package com.dbs.mplus.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.dbs.mplus.MPlusConstants;
import com.dbs.mplus.exception.mobile.ApiCallException;
import com.dbs.mplus.framework.FrameworkConstants;
import com.dbs.mplus.framework.exception.service.LoginException;
import com.dbs.mplus.framework.service.admin.userAttr.UserAttrService;
import com.dbs.mplus.framework.service.admin.userAttr.repository.dao.UserAttr;
import com.dbs.mplus.framework.service.mobile.user.MpLoginInfoService;
import com.dbs.mplus.framework.service.mobile.user.UserAuthService;
import com.dbs.mplus.framework.service.mobile.user.repository.dao.UserAuth;
import com.dbs.mplus.framework.util.HashUtil;
import com.dbs.mplus.service.api.MPlusApi;
import com.dbs.mplus.service.api.MPlusApiCallService;
import com.dbs.mplus.service.mobile.user.MPUserService;
import com.dbs.mplus.service.mobile.user.repository.dao.MPlusUser;

/**
 * 사용자를 인증하고, 어느 페이지로 넘어가야할지 체크한다. 
 * 
 * @author jinsuk, heesung 
 * 
 */
@Service
public class AuthenticationDelegator {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Autowired
	private RememberMeCookieBaker cookieBaker;
	@Autowired 
	private MPlusApiCallService apiCallService; 
	@Autowired 
	private MPlusConfigureService configureService; 

	@Autowired
	private UserAuthService userAuthService;
	@Autowired
	private UserAttrService userAttrService;
	
	@Autowired
	private MPUserService userService;
	@Autowired
	private MpLoginInfoService mpLoginInfoService;
	
	private HashUtil hashUtils; 
	
	@PostConstruct 
	public void afterPropertiesSet() { 
		this.hashUtils = new HashUtil(this.configureService.getHashSalt()); 
	}
	
	
	/**
	 * 로그인 처리 
	 * 20190125 shinsunwoo device id, user ip 추출하여 파라미터 전달
	 * @param request
	 * @param response
	 * @param userId
	 * @param plainPassWord
	 * @param forcedPage 
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	@Transactional(value = "mplusTransactionManager", rollbackFor = { Exception.class })
	public MPlusUser authentication(HttpServletRequest request, HttpServletResponse response, String userId, String plainPassWord, String hospitalCd, String deviceId, String forcedPage) throws Exception {
	
		Map<String, Object> reqMap = new HashMap<String, Object>();
		List<Map<String, Object>> legacyResultList = null; 
		
		// 요청 파라미터 map 만들기
		reqMap.put("userId", userId);
		reqMap.put("userPw", plainPassWord);
		reqMap.put("hospitalCd", hospitalCd);
		reqMap.put("userDeviceId", deviceId);
		reqMap.put("userIP", com.dbs.mplus.framework.util.HttpRequestUtil.getClientIpAddr(request));
				
		// 병원 서버에 로그인 요청하기
		try { 
			legacyResultList = (List<Map<String, Object>>) this.apiCallService.execute(MPlusApi.LOGIN_DOLOGIN, reqMap); 
		} catch(ApiCallException ex) { 
			ex.printStackTrace();
			// 병원 API 처리에 실패한 경우 
			throw new LoginException("mplus.error.if.system", "시스템 호출 오류가 발생했습니다 (IF)", null);		
		}
		
		// 로그인 실패 판별 (병원이 주는 결과값에 따라 달라질 수 있음)
		if( legacyResultList.size() == 0 ) {
			throw new LoginException("mplus.auth.not.department", "소속된 진료과가 없습니다. 접속권한을 확인하세요", null);
		}
		
		if( legacyResultList.size() == 1 ) {
			if( legacyResultList.get(0).get("userId") == null) {
				throw new LoginException("mplus.auth.checkaccount", "아이디 또는 비밀번호를 다시 확인하세요", null);
			}
		}
		
		// 20181221 shinsunwoo 로그인 응답 코드 확인, returnCd, returnMsg, failCnt 체크
		if( !legacyResultList.get(0).get("returnCd").toString().equals("0000")){
			// mplus.auth.checkaccount : client에는 response.msg로 '아이디 또는 비밀번호를 다시 확인하세요'가 들어가게 됨
			//throw new LoginException("mplus.auth.checkaccount", legacyResultList.get(0).get("returnMsg").toString(), null);
			MPlusUser mpUser = new MPlusUser();
			mpUser.setUserId(legacyResultList.get(0).get("userId").toString());
			mpUser.setHospitalCd(legacyResultList.get(0).get("hospitalCd").toString());
			mpUser.setReturnCd( legacyResultList.get(0).get("returnCd").toString());
			mpUser.setReturnMsg( legacyResultList.get(0).get("returnMsg").toString());
			mpUser.setFailCnt( legacyResultList.get(0).get("failCnt").toString());
			return mpUser;
		}	
		
		// 세브란스 - 부서 정리
		Map<String, Object> userMap = legacyResultList.get(0);
		List<Map<String, Object>> departmentList = new ArrayList<Map<String, Object>>();
		for( int i = 0; i < legacyResultList.size(); i++ ) {
			Map<String, Object> department = new HashMap<String, Object>();
			department.put("userId", legacyResultList.get(i).get("userId"));
			department.put("userNm", legacyResultList.get(i).get("userNm"));
			department.put("deptCd", legacyResultList.get(i).get("deptCd"));
			department.put("deptNm", legacyResultList.get(i).get("deptNm"));
			department.put("jobKindCd", legacyResultList.get(i).get("jobKindCd"));
			department.put("jobKindNm", legacyResultList.get(i).get("jobKindNm"));
      department.put("userAuth", legacyResultList.get(i).get("userAuth"));
      department.put("deptabbr", legacyResultList.get(i).get("deptabbr"));
      department.put("dutplceinstnm", legacyResultList.get(i).get("dutplceinstnm"));
      department.put("dutplcenm", legacyResultList.get(i).get("dutplcenm"));
      department.put("jobposnm", legacyResultList.get(i).get("jobposnm"));
      department.put("posdeptnm", legacyResultList.get(i).get("posdeptnm"));
      department.put("posinstnm", legacyResultList.get(i).get("posinstnm"));
      department.put("prfshipflagnm", legacyResultList.get(i).get("prfshipflagnm"));
      department.put("systeminstnm", legacyResultList.get(i).get("systeminstnm"));
      department.put("systemnm", legacyResultList.get(i).get("systemnm"));
      department.put("unitnm", legacyResultList.get(i).get("unitnm"));
      department.put("jobrespnm", legacyResultList.get(i).get("jobrespnm"));
      
//			if( "W".equals(legacyResultList.get(i).get("jobKindCd"))) {
//				department.put("departmentTyp", "W");
//			} else {
//				department.put("departmentTyp", "D");
//			}
			departmentList.add(department);
		}
		
		userMap.put("departmentList", departmentList);
		MPlusUser mpUser = new MPlusUser();
		mpUser.setUserId(legacyResultList.get(0).get("userId").toString());
		mpUser.setUserNm((String) userMap.get("userNm"));
		mpUser.setHospitalCd(hospitalCd);
		
		
		
		/*-------------------사용자가 MP Database에 저장될 필요가 없는 경우(속성을 사용하지 않는 경우) 아래 내용을 주석처리 하면 됩니다.--------------*/
		// MP 사용자로 등록되어 있는지 체크
/*		mpUser = userService.get(mpUser);

		// 등록되어있지 않다면
		if( mpUser == null ) {
			mpUser = new MPlusUser();
			mpUser.setUserId(userId);
			mpUser.setUserNm((String) userMap.get("userNm"));
			
			// 1. 사용자등록
			this.logger.debug("MPLUS 사용자로 등록되어 있지 않음: " + userId);
			userService.insertUser(userId, mpUser.getUserNm(), this.hashUtils.sha256(plainPassWord));
			// 2. 속성 등록
			// legacyResultMap.get("attr")가 없을 때 에러처리 
			// 병원에서 내려준 값 중에 key가 attr인 항목으로 속성을 판별한다고 가정
			
			// 병원 API에서 내려준 속성정보 parsing > DB에 등록
			List<UserAttr> userAttrList = makeUserAttrList(userId, (String) legacyResultMap.get("attr"));
			userAttrService.add( userAttrList );
			
			// 속성이 한개이면 해당 속성을 사용자의 선택속성으로 지정, 두개 이상일 경우 속성선택을 사용자에게 직접 받기 위해서 아무 액션도 하지 않는다.
			if( userAttrList.size() == 1 ) {
				Map<String, Object> param = new HashMap<String, Object>();
				param.put("userId", userId);
				param.put("useAttrOrder", 1l);
				mpUser.setUseAttrOrder(1l);
				userAttrService.modifyUserUseAttrOrder(param);
			}

			mpUser.setUserAttrList(userAttrList);
		}
		mpUser.setHospitalCd(hospitalCd);*/
		
		/*-----------------------------------여기까지 주석처리 하면 됩니다---------------------------------------------*/
		
		// 병원 API 에서 내려주는 정보 중에 필요한 내용들을 사용자 정보에 셋팅
		mpUser.setUserNm((String) userMap.get("userNm"));
		mpUser.setDepartmentList((List<Map<String, Object>>) userMap.get("departmentList"));
		
		// 세브란스 - 간호사의 소속이 병동이지 구분하기위해 쓰인 W는 N(간호사)으로 변경
		String jobKindCd = (String) userMap.get("jobKindCd");
		if("W".equals(jobKindCd)) {
			jobKindCd = "N";
		}
		mpUser.setJobKindCd(jobKindCd);
		mpUser.setJobKindNm((String) userMap.get("jobKindNm"));
		
		// 세션에 사용자 정보 심기 
		// 20190311 shinsunwoo (주의) mcare-plus-framework의 로그인 세션 생성시 마지막setAttribute에 SESSION.USER_DEVICE_ID를 체크하므로
		// 세션 수정 시, 마지막setAttribute는 SESSION SESSION.USER_DEVICE_ID를 유지해 주세요.
		final HttpSession session = request.getSession();
		// 사용자 ID 
		session.setAttribute(FrameworkConstants.SESSION.USER_ID.getKey(), mpUser.getUserId());
		// 사용자 이름
		session.setAttribute(FrameworkConstants.SESSION.USER_NAME.getKey(), mpUser.getUserNm());
		// Hospital Code
		session.setAttribute(FrameworkConstants.SESSION.USER_HOSPITAL.getKey(), mpUser.getHospitalCd());
		// Job kind code
		session.setAttribute(MPlusConstants.SESSION.JOB_KIND_CD.getKey(), mpUser.getJobKindCd());
		// Job kind name
		session.setAttribute(MPlusConstants.SESSION.JOB_KIND_NM.getKey(), mpUser.getJobKindNm());
		// Device ID : 20190116 shinsunwoo 중복로그인 체크용 Device ID 적용
		session.setAttribute(FrameworkConstants.SESSION.USER_DEVICE_ID.getKey(), deviceId);

		// 20190118 shinsunwoo 중복로그인 체크, user id, user uuid를 DB에 insert or upate
		// 단독 db, 비부하 상태에서 등록 테스트에서 초당 188건 등록
		if( this.configureService.isSessionDuplicateCheck()) {
			mpLoginInfoService.insertUuid(userId, deviceId);	// DB insert or update
			this.logger.debug("LoginInfo aaa  = " + userId);
		}
		
		//사용자 권한 목록 가져와서 session에 담기
/*		List<UserAuth> userAuths = this.userAuthService.getAuthAttrListByAuthId(userId, mpUser.getUseAttrOrder());
		session.setAttribute(FrameworkConstants.SESSION.USER_AUTH_LIST.getKey(), userAuths);
*/
		// 사용자가 자기 손으로 순순히 로그인 하는 경우에는 대부분 index page로 가야 하지만 
		// 로그인하지 않은 상태에서 push를 받고, 로그인 한 다음에 어디로 가는 경우라던지 (2016.2.2.현재. 아직 고려되지 않고 있는 서비스 경로임) 
		// 로그인 된 상태에서 세션이 끊어졌는데 로그인이 필요한 어떤메뉴(예:번호표발급)를 누른 경우 자동로그인을 탄 다음에 해당 메뉴로 곧장 가기 위해 필요함 
		if(forcedPage != null) {
			// 이름은 page이지만, 사실 json요청일수도 있음 
			mpUser.setNextPage(forcedPage);
			// index로 보낼려면 이렇게.... 
			//user.setNextPage(MPlusConstants.URI_MP_INDEX);
		}

		return mpUser;
	}
	
	/**
	 * 병원 API에서 받은 속성 데이터를 파싱함 (병원마다 형태가 다를 수 있음)
	 * ParamMappingUtil.java에서 로그인 응답값에 대해서 편집한 내용을 (병원에서 내려준 속성코드를 mplus에서 쓰는 속성코드로 변환 / 포맷정리 등) 
	 * 다시 UserAttr class로 변환하는 함수
	 * 
	 * 병원 API에서 속성값이 문자열인 경우에 대한 예시
	 * 병원 API에서 실제로는 (소속직무업무|소속직무업무...) 이렇게 내려주고, ParamMappingUtil 에서 소속/직무/업무|소속/직무/업무 로 변환한다고 가정함
	 * 
	 * @param userId
	 * @param attrStr
	 * @return
	 */
	private List<UserAttr> makeUserAttrList(String userId, String attrStr) {

		if( attrStr == null ) {
			throw new LoginException("mplus.auth.not.userattr", "등록된 사용자 속성이 없습니다. 관리자에게 문의하세요.", null);
		}
		
		List<UserAttr> userAttrList = new ArrayList<UserAttr>();
		String[] attrList = attrStr.split("\\|");
		
		// 속성이 하나도 없는 경우 로그인 실패
		if(attrList.length == 0) {
			throw new LoginException("mplus.auth.not.userattr", "등록된 사용자 속성이 없습니다. 관리자에게 문의하세요.", null);
		}
		
		for( int i = 0; i < attrList.length; i++ ){
			UserAttr userAttr = new UserAttr();
			String attr = attrList[i];
			String[] attrItem = attr.split("/");
			// 소속/직무/업무 항목 세가지가 제대로 들어오지 않으면 병원에서 내려주는 속성값에 문제가 있다고 판단하고 로그인 실패
			if(attrItem.length != 3) {
				throw new LoginException("mplus.auth.not.userattr", "등록된 사용자 속성이 없습니다. 관리자에게 문의하세요.", null);
			}
			userAttr.setUserId(userId);
			userAttr.setUserAttrOrder(i+1l);
			userAttr.setHospitalId(attrItem[0]);
			userAttr.setDutyId(attrItem[1]);
			userAttr.setWorkId(attrItem[2]);
			userAttrList.add(userAttr);
		}
		
		return userAttrList;
		
	}
}