package gov.va.med.authentication.kernel;

import gov.va.med.vistalink.adapter.cci.VistaLinkConnection;
import gov.va.med.vistalink.adapter.cci.VistaLinkConnectionSpec;
import gov.va.med.vistalink.adapter.cci.VistaLinkDuzConnectionSpec;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import weblogic.security.SimpleCallbackHandler;
import weblogic.servlet.security.ServletAuthentication;




/**
 * Methods are for internal KAAJEE use only. This servlet class implements the controller pattern for the forms 
 * authentication login pages. The 
 * KAAJEE login.jsp page submits to this servlet, which in turn processes the request and forwards/redirects 
 * to the appropriate destination -- either an error page, back to login.jsp for a retry, or to the 
 * j_security_check magic servlet.<p>
 * The enclosing application should configure this servlet in the web.xml deployment descriptor, as follows:
 * <pre>
 *   &lt;servlet&gt;
 *     &lt;servlet-name&gt;LoginController&lt;/servlet-name&gt;
 *     &lt;servlet-class&gt;gov.va.med.authentication.kernel.LoginController&lt;/servlet-class&gt;
 *   &lt;/servlet&gt;
 *   &lt;servlet-mapping&gt;
 *     &lt;servlet-name&gt;LoginController&lt;/servlet-name&gt;
 *     &lt;url-pattern&gt;/LoginController&lt;/url-pattern&gt;
 *   &lt;/servlet-mapping&gt;
 * </pre>
 * @author VHIT Security and Other Common Services (S&OCS)
 * @version 1.1.0.007
 */
public class LoginController extends HttpServlet {

	private static Logger logger = Logger.getLogger(LoginController.class);
	
	private static final String FORM_INSTITUTION_FIELD_NAME = "institution";
	private static final String FORM_ACCESS_CODE_FIELD_NAME = "access";
	private static final String FORM_VERIFY_CODE_FIELD_NAME = "verify";
	
	private static final String FORM_SORT_INSTITUTION_BY_FIELD_NAME = "sortInstBy";
	private static final String FORM_SUBMIT_FIELD_NAME = "submit";

	private static final String SESSION_KEY_ACCESS = "gov.va.med.authentication.kernel.access";
	private static final String SESSION_KEY_VERIFY = "gov.va.med.authentication.kernel.verify";
	private static final String SESSION_KEY_DIVISION = "gov.va.med.authentication.kernel.division";

	private static final String SESSION_KEY_ERROR_MESSAGE = "gov.va.med.authentication.kernel.errormessage";

	private static final int COOKIE_EXPIRY_DAYS = 180;
	
	static final String LOGIN_RPC_CONTEXT = "XUS KAAJEE WEB LOGON"; // Reset to original value: "XUS KAAJEE WEB LOGON";

	/**
	 * session key used to store the user's last login division
	 */
	public static final String SESSION_KEY_COOKIE_DEFAULT_DIVISION = "gov.va.med.authentication.kernel.cookieDivision";

	/**
	 * cookie key used to store default division inside a persistent cookie
	 */
	public static final String COOKIE_DEFAULT_DIVISION_STRING = "gov.va.med.authentication.kernel.defaultDivision";
	
	/**
	 * session key used to store the user's last preference for sorting institutions
	 */
	public static final String SESSION_KEY_COOKIE_DEFAULT_SORT_INSTITUTION_BY = "gov.va.med.authentication.kernel.cookieSortInstitutionBy";
	
	/**
	 * cookie key used to store default sorting preference for the institution drop down inside a persistent cookie
	 */
	public static final String COOKIE_DEFAULT_SORT_INSTITUTION_BY_STRING = "gov.va.med.authentication.kernel.defaultSortInstitutionBy";

	/**
	 * implements required doGet method.
	 * @param request HttpServletRequest object
	 * @param response HttpServletResponse object
	 * @throws ServletException if problem encountered.
	 * @throws IOException if problem encountered.
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		routeRequest(request, response);
	}

	/**
	 * implements required doPost method.
	 * @param request HttpServletRequest object
	 * @param response HttpServletResponse object
	 * @throws ServletException if problem encountered.
	 * @throws IOException if problem encountered.
	 */
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		routeRequest(request, response);
	}

	/**
	 * provides uniform processing for both POST and GET requests
	 * @param request HttpServletRequest object
	 * @param response HttpServletResponse object
	 * @throws ServletException standard servlet exception
	 * @throws IOException if problem encountered
	 */
	private void routeRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
			
		if (logger.isEnabledFor(Level.DEBUG)) {
			logger.debug("In doPage. Client host: " + request.getRemoteAddr());
		}

		//HttpSession session = request.getSession(true);
		// remove any existing error message

		
		HttpSession session = request.getSession(false);
		if(session == null) {
			session = request.getSession(true);
			RequestDispatcher rd = getServletContext().getRequestDispatcher("/login/SessionTimeout.jsp");
			rd.forward(request,response);
			return;
		} 

				
		session.removeAttribute(SESSION_KEY_ERROR_MESSAGE);

		// get access, verify, institution from form (if present)
		String formInstitutionValue = request.getParameter(FORM_INSTITUTION_FIELD_NAME);
		String formAvPairValue = request.getParameter(FORM_ACCESS_CODE_FIELD_NAME);
		String formAccessValue;
		String formVerifyValue;
		String[] formAvArray;
		if ((formAvPairValue != null) && (formAvPairValue.indexOf(';') > -1)) {
			formAvArray = formAvPairValue.split("\u003B");
			formAccessValue = formAvArray[0];
			formVerifyValue = formAvArray[1];
		} else {
			formAccessValue = formAvPairValue;
			formVerifyValue = request.getParameter(FORM_VERIFY_CODE_FIELD_NAME);
		}
		
		// get sortInstBy and submit from form (if present)
		String formSortInstitutionByValue = request.getParameter(FORM_SORT_INSTITUTION_BY_FIELD_NAME);
		String formSubmitValue = request.getParameter(FORM_SUBMIT_FIELD_NAME);

		// get access, verify, institution from sesssion (if present)		
		String sessionInstitutionValue = (String) session.getAttribute(SESSION_KEY_DIVISION);
		String sessionAccessValue = (String) session.getAttribute(SESSION_KEY_ACCESS);
		String sessionVerifyValue = (String) session.getAttribute(SESSION_KEY_VERIFY);

		// get LoginUserInfoVO if ccow VistA token was successfully validated
		LoginUserInfoVO sessionUserInfo = (LoginUserInfoVO) session.getAttribute(LoginUserInfoVO.SESSION_KEY);
		
		if (logger.isEnabledFor(Level.DEBUG)) {
			logger.debug("formSortInstitutionByValue = " + formSortInstitutionByValue);
			logger.debug("formSubmitValue = " + formSubmitValue);
			logger.debug("LoginUserInfoVO from ccow validation = " + sessionUserInfo);
		}
		
		if (sessionUserInfo != null){
			processCredentialsAndLogIn(request,
									   response,
									   sessionInstitutionValue,
									   sessionAccessValue,
									   sessionVerifyValue);
			return;
		}
		
		if ((sessionAccessValue != null) && (sessionVerifyValue != null) && (sessionInstitutionValue != null)) {
			
			// if a/v code and institution are in Session, then this must be a return from 
			//    the "cookie rewrite client-side redirect".
			
			session.removeAttribute(SESSION_KEY_ACCESS);
			session.removeAttribute(SESSION_KEY_VERIFY);
			session.removeAttribute(SESSION_KEY_DIVISION);
			// process the login
			processCredentialsAndLogIn(
				request,
				response,
				sessionInstitutionValue,
				sessionAccessValue,
				sessionVerifyValue);

		} else {
			// this is a direct submit of access/verify/institution, presumably from login.jsp

			//If cookies need to be updated, redirect to client for update
			if (cookieNeedToBeUpdated(request, response, session, formInstitutionValue, formAccessValue, formVerifyValue, formSortInstitutionByValue)) {
				StringBuffer sb = new StringBuffer(request.getContextPath());
				sb.append("/LoginController");					
				logger.debug("About to redirect to write persistent cookie, to: " + sb.toString());
				response.sendRedirect(sb.toString());				
			} else {
				//process the login
				processCredentialsAndLogIn(request, response, formInstitutionValue, formAccessValue, formVerifyValue);
			}
		}
	}

	private void processCredentialsAndLogIn(
		HttpServletRequest request,
		HttpServletResponse response, 
		String institution,
		String access,
		String verify)
		throws ServletException, IOException {

		String jUsername = null;
		String jPassword = null;
		VistaLinkConnection myConnection = null;
		LoginUserInfoVO userInfo = null;
		HttpSession session = request.getSession(true);
		String clientIp = request.getRemoteAddr();
		//ConfigurationVO contextName = ConfigurationVO.getInstance();
		

		try {
			//TODO cancel if in cactus mode? (would be a good warning to adminstrators that system is 
			//     misconfigured)
			
			// get LoginUserInfoVO if ccow VistA token was successfully validated
			userInfo = (LoginUserInfoVO) session.getAttribute(LoginUserInfoVO.SESSION_KEY);
			if (userInfo != null){
				// userInfo came from validation of ccow VistA token
				// will need a VistaLinkConnection for calls downstream
				logger.debug("ccow");
				String loginStationNumber = userInfo.getLoginStationNumber();
				String duz = userInfo.getUserDuz();
				VistaLinkConnectionSpec connSpec = new VistaLinkDuzConnectionSpec(loginStationNumber, duz);
				myConnection = LoginControllerUtils.getVistaLinkConnection(loginStationNumber,connSpec);
			} else {
				// userInfo is null, process the other credentials and login
				// 1. check form submission values.
				logger.debug("1");
				LoginControllerUtils.checkFormInputValues(institution, access, verify);
				
				// 2. Now check if we're OK as far as the login Institution choice is concerned.
				logger.debug("2");
				LoginControllerUtils.validateKaajeeLoginDivision(institution);
				// 3. get the connection
				logger.debug("3");
				// VistaLinkAVConnectionSpec avConnSpec = new VistaLinkAVConnectionSpec(institution, access, verify);
				KaajeeVistaLinkConnectionSpec avConnSpec = new KaajeeVistaLinkConnectionSpec(institution, access, verify, clientIp);
				myConnection = LoginControllerUtils.getVistaLinkConnection(institution, avConnSpec);
				// 4. Create LoginUserInfoVO object, put in Session
				logger.debug("4");
				userInfo = LoginUserInfoVOAssembler.getLoginUserInfo(myConnection, institution, clientIp);
				session.setAttribute(LoginUserInfoVO.SESSION_KEY, userInfo);
			}
			// 5. Create j_username and j_password
			logger.debug("5");
			jUsername = LoginControllerUtils.createJUsername(userInfo);
			jPassword = LoginControllerUtils.createJPasswordForWebUser();
			LoginControllerUtils.createAuthenticateUser(jUsername,jPassword);
			
			//LoginControllerUtils.addMemberToGroup(jUsername,"PATSSIT");
			// 6. Cache roles
			logger.debug("6");
			LoginControllerUtils.cacheUserRoles(jUsername, myConnection);
			// 7. store one-time token
			logger.debug("7");
			//LoginControllerUtils.storeOneTimeToken(jUsername, jPassword);
			// 8. do the user manager redirect
			logger.debug("8");
			doUserManagerRedirect(request, response, jUsername, jPassword);

		} catch (KaajeeException e) {

			String exceptionString = "Error processing login credentials: ";
			
			if (logger.isEnabledFor(Level.DEBUG)) {
				logger.debug(exceptionString, e);
			}
			doLoginErrorRedirect(request, response, exceptionString + e.getMessage());

		} finally {

			// we're done with M connections now.
			LoginControllerUtils.closeVistaLinkConnection(myConnection);
		}
	}

	/**
	 * Return valid j_username and j_password for use with Cactus testing.
	 * @param institution login institution
	 * @param access access code to use
	 * @param verify verify code to use
	 * @param session an optional HttpSession object to return LoginUserInfoVO in. Pass null if not needed.
	 * @return valid j_username and j_password to use for repeated logins during unit testing
	 * @throws KaajeeException thrown if can't get credentials for some reason
	 */
	public static CactusFormsAuthCredentialVO getFormsAuthCredentialsForCactus(
		String institution,
		String access,
		String verify,
		HttpSession session)
		throws KaajeeException {

		String jUsername = null;
		String jPassword = null;
		VistaLinkConnection myConnection = null;
		LoginUserInfoVO userInfo = null;
		CactusFormsAuthCredentialVO returnVal = null;
		String clientIp = "cactusTest";

		ConfigurationVO configVO = ConfigurationVO.getInstance();
		// check if security violation
		if (!configVO.getIsCactusModeEnabled()) {

			String exceptionString =
				"Warning: Attempt to use Cactus mode without that mode enabled in the KAAJEE configuration. Denying login attempt.";
			logger.error(exceptionString);
			throw new KaajeeException(exceptionString);

		} else {

			try {

				// 1. check form submission values.
				LoginControllerUtils.checkFormInputValues(institution, access, verify);
				logger.debug("CactusFormsAuthCredentialVO: finished step 1");
				// 2. Now check if we're OK as far as the login Institution choice is concerned.
				LoginControllerUtils.validateKaajeeLoginDivision(institution);
				logger.debug("CactusFormsAuthCredentialVO: finished step 2");
				// 3. get the connection
				KaajeeVistaLinkConnectionSpec avConnSpec =
					new KaajeeVistaLinkConnectionSpec(institution, access, verify, clientIp);
				myConnection = LoginControllerUtils.getVistaLinkConnection(institution, avConnSpec);
				logger.debug("CactusFormsAuthCredentialVO: finished step 3");
				// 4. Create LoginUserInfoVO object, put in Session
				userInfo = LoginUserInfoVOAssembler.getLoginUserInfo(myConnection, institution, clientIp);
				if (session != null) {
					session.setAttribute(LoginUserInfoVO.SESSION_KEY, userInfo);
				}
				logger.debug("CactusFormsAuthCredentialVO: finished step 4");
				// 5. Create j_username and j_password
				jUsername = LoginControllerUtils.createJUsername(userInfo);
				jPassword = LoginControllerUtils.createJPasswordForCactus();
				logger.debug("CactusFormsAuthCredentialVO: finished step 5");
				// 6. Cache roles
				LoginControllerUtils.cacheUserRoles(jUsername, myConnection);
				logger.debug("CactusFormsAuthCredentialVO: finished step 6");
				//	create jUsername(username),jPassword(OneTimeToken) into the principals table
				LoginControllerUtils.createAuthenticateUser(jUsername,jPassword);
				// 7. store one-time token
				//LoginControllerUtils.storeOneTimeToken(jUsername, jPassword);
				//logger.debug("CactusFormsAuthCredentialVO: finished step 7");
				// 8. store return value
				returnVal = new CactusFormsAuthCredentialVO(jUsername, jPassword);
				logger.debug("CactusFormsAuthCredentialVO: finished step 8");
							

			} catch (KaajeeException e) {

				String exceptionString = "Error processing Cactus/UnitTest login credentials: ";
				if (logger.isEnabledFor(Level.DEBUG)) {
					logger.debug(exceptionString, e);
				}
				throw e;

			} finally {

				// we're done with M connections now.
				LoginControllerUtils.closeVistaLinkConnection(myConnection);
			}
		}
		return returnVal;
	}

	/**
	 * 
	 * @param request
	 * @param response
	 * @param session
	 * @param institution
	 * @param access
	 * @param verify
	 * @param sortInstitutionByValue
	 * @throws ServletException
	 * @throws IOException
	 */
	private boolean cookieNeedToBeUpdated(
		HttpServletRequest request,
		HttpServletResponse response,
		HttpSession session,
		String institution,
		String access,
		String verify,
		String sortInstitutionByValue)
		throws ServletException,IOException {
		
		boolean returnVal = false;
		
		// if default division not stored in client cookie, or if present but different than submitted form
		//     value, redirect to client to write a cookie saving the submitted division in a cookie for 
		//     subsequent logins
		String cookieDefaultDivision = (String) session.getAttribute(SESSION_KEY_COOKIE_DEFAULT_DIVISION);
		if (logger.isEnabledFor(Level.DEBUG)) {
			StringBuffer sb = new StringBuffer("cookieDefaultDivision = ");
			sb.append(cookieDefaultDivision);
			sb.append("formInstitutionValue = ");
			sb.append(institution);
			sb.append("request.getRequestURI() = ");
			sb.append(request.getRequestURI());
			logger.debug(sb.toString());
			}
		if ((cookieDefaultDivision == null) || (!cookieDefaultDivision.equals(institution))) {
			// create a cookie, redirect to client and back to write the cookie
			Cookie institutionCookie = new Cookie(COOKIE_DEFAULT_DIVISION_STRING, institution);
			institutionCookie.setMaxAge(60 * 60 * 24 * COOKIE_EXPIRY_DAYS);
			response.addCookie(institutionCookie);
			returnVal = true;
		}
		// if default institution sorting preference is not stored in client cookie, or if present but different than submitted form
		//     value, redirect to client to write a cookie saving the submitted sorting preference in a cookie for 
		//     subsequent logins
		String cookieDefaultSortInstitutionBy = (String) session.getAttribute(SESSION_KEY_COOKIE_DEFAULT_SORT_INSTITUTION_BY);
		if (logger.isEnabledFor(Level.DEBUG)) {
			StringBuffer sb = new StringBuffer("cookieDefaultSortInstitutionBy = ");
			sb.append(cookieDefaultSortInstitutionBy);
			sb.append("formSortInstitutionByValue = ");
			sb.append(sortInstitutionByValue);
			sb.append("request.getRequestURI() = ");
			sb.append(request.getRequestURI());
			logger.debug(sb.toString());
			}
		if ((cookieDefaultSortInstitutionBy == null) || (!cookieDefaultSortInstitutionBy.equals(sortInstitutionByValue))) {
			// create a cookie, redirect to client and back to write the cookie
			Cookie sortInstitutionByCookie = new Cookie(COOKIE_DEFAULT_SORT_INSTITUTION_BY_STRING, sortInstitutionByValue);
			sortInstitutionByCookie.setMaxAge(60 * 60 * 24 * COOKIE_EXPIRY_DAYS);
			response.addCookie(sortInstitutionByCookie);
			returnVal = true;
		}
		if (returnVal) {
			session.setAttribute(SESSION_KEY_DIVISION, institution);
			session.setAttribute(SESSION_KEY_ACCESS, access);
			session.setAttribute(SESSION_KEY_VERIFY, verify);
		}
		return returnVal;
	}

	/**
	 * private method to redirect to the j_security_check magic servlet to submit the username and password
	 * to the user manager.
	 * @param request
	 * @param response
	 * @param j_username
	 * @param j_password
	 * @throws ServletException
	 */
	private void doUserManagerRedirect(
		HttpServletRequest request,
		HttpServletResponse response,
		String jUsername,
		String jPassword)
		throws ServletException, IOException {

		if (logger.isEnabledFor(Level.DEBUG)) {
			logger.debug("in doUserManagerRedirect");
		}
		// ServletAuthentication.authenticate API is used instead of submitting j_username and j_password to j_security_check.
		// This is done to provide more flexibility for SSL support.
		 
		SimpleCallbackHandler callbackHandler = new SimpleCallbackHandler(jUsername, jPassword.getBytes());
		if (logger.isEnabledFor(Level.DEBUG)) {
			StringBuffer sb = new StringBuffer("About to call ServletAuthentication.authenticate API ");
			sb.append("to authenticate user: '");
			sb.append(jUsername);
			sb.append("'.");
			logger.debug(sb.toString());
		}
		int result = ServletAuthentication.authenticate(callbackHandler, request);
		String targetURL = ServletAuthentication.getTargetURLForFormAuthentication(request.getSession());
		if (result == ServletAuthentication.AUTHENTICATED) {
			if (logger.isEnabledFor(Level.DEBUG)) {
				StringBuffer sb = new StringBuffer("Succesfully Authenticated");
				logger.debug(sb.toString());
				}
		} else {
			if (logger.isEnabledFor(Level.DEBUG)) {
				StringBuffer sb = new StringBuffer("Authentication Failed ! ! !");
				logger.debug(sb.toString());
				}
		}
		if (logger.isEnabledFor(Level.DEBUG)) {
			StringBuffer sb = new StringBuffer("targetURL = ");
			sb.append(targetURL);
			logger.debug(sb.toString());
			}
		if (result == ServletAuthentication.AUTHENTICATED) {
			response.sendRedirect(targetURL);
		} else {
			response.sendRedirect("/login/loginerror.jsp");
		}
	}
		
	/**
	* private method to redirect to the loginerrordisplay.jsp if an error condition is encountered prior to submitting
	* to the j_security_check magic servlet.
	* @param request
	* @param response
	* @param errorMessage
	* @throws ServletException
	* @throws IOException
	*/
	private void doLoginErrorRedirect(HttpServletRequest request, HttpServletResponse response, String errorMessage)
		throws ServletException, IOException {

		if (logger.isEnabledFor(Level.DEBUG)) {
			logger.debug("in doLoginErrorRedirect");
		}

		// remove any KAAJEE values from session
		HttpSession session = request.getSession(true);
		session.removeAttribute(SESSION_KEY_ACCESS);
		session.removeAttribute(SESSION_KEY_VERIFY);
		session.removeAttribute(SESSION_KEY_DIVISION);
		session.removeAttribute(LoginUserInfoVO.SESSION_KEY);
		session.removeAttribute(SESSION_KEY_COOKIE_DEFAULT_DIVISION);

		// put error message in session
		session.setAttribute(SESSION_KEY_ERROR_MESSAGE, errorMessage);
		RequestDispatcher rd = getServletContext().getRequestDispatcher("/login/loginerrordisplay.jsp");
		if (rd == null) {
			String exceptionMessage = "Could not retrieve RequestDispatcher for /login/loginerrordisplay.jsp.";
			if (logger.isEnabledFor(Level.ERROR)) {
				logger.error(exceptionMessage);
			}
			throw new ServletException(exceptionMessage);
		} else {
			rd.forward(request, response);
		}
	}
	
}
