package gov.va.med.authentication.kernel;

import gov.va.med.term.access.Institution;
import gov.va.med.vistalink.adapter.cci.VistaLinkConnection;
import gov.va.med.vistalink.adapter.cci.VistaLinkConnectionFactory;
import gov.va.med.vistalink.adapter.cci.VistaLinkConnectionSpec;
import gov.va.med.vistalink.institution.InstitutionMapNotInitializedException;
import gov.va.med.vistalink.institution.InstitutionMappingDelegate;
import gov.va.med.vistalink.institution.InstitutionMappingNotFoundException;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Random;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.resource.ResourceException;

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


/**
 * Utility calls used by the LoginController.
 * @author VHIT Security and Other Common Services (S&OCS)
 * @version 1.1.0.007
 */
class LoginControllerUtils {

	private static Logger logger = Logger.getLogger(LoginControllerUtils.class);
	static final String J_PASSWORD_PREFIX_WEB = "WEB";
	static final String J_PASSWORD_PREFIX_CACTUS = "TST";
	static final String J_USERNAME_DUZ = "DUZ_";
	static final String J_USERNAME_COMPSYS = "~CMPSYS_";

	/**
	 * close a VistaLink connection
	 * @param myConnection
	 */
	static void closeVistaLinkConnection(VistaLinkConnection myConnection) {
		if (myConnection != null) {
			try {
				myConnection.close();
			} catch (ResourceException e) {
				//TODO -- anything besides swallow?
			}
		}
	}

	/**
	 * use to do basic input value checking on form submission values
	 * @param institution login institution submitted
	 * @param access access code submitted
	 * @param verify verify code submitted
	 * @throws KaajeeException thrown if input found to be invalid.
	 */
	static void checkFormInputValues(String institution, String access, String verify) throws KaajeeException {
		{
			// TODO add some regexp checking here too.
			if (((institution == null) || (access == null) || (verify == null))
				|| ((institution.length() < 1) || (access.length() < 1) || (verify.length() < 1))) {

				StringBuffer sb = new StringBuffer("Problem with submitted login information: ");
				if ((access == null) || (access.length() < 1)) {
					sb.append("Access code not provided");
				}
				if ((verify == null) || (verify.length() < 1)) {
					if (sb.length() > 0) {
						sb.append("; ");
					}
					sb.append("Verify code not provided");
				}
				if ((institution == null) || (institution.length() < 1)) {
					if (sb.length() > 0) {
						sb.append("; ");
					}
					sb.append("Login facility not provided");
				}
				sb.append(".");
				throw new KaajeeException(sb.toString());
			}
		}
	}

	/**
	 * Validates a candidate "login institution" against several criteria.
	 * @param institutionStNum station number to validate
	 * @throws KaajeeException thrown if institution is invalid.
	 */
	static void validateKaajeeLoginDivision(String institutionStNum)
		throws KaajeeInstitutionResourceException, KaajeeException {

		// 1. verify that is in Institution table, and has a legitimate Vista Provider too. (An exception
		//    is thrown if not)
		getValidatedVistaProvider(institutionStNum);

		// 2. verify that it's in the KAAJEE login list		
		ConfigurationVO configVO = ConfigurationVO.getInstance();
		if (!configVO.isKaajeeLoginDivision(institutionStNum)) {
			// if not in the KAAJEE login file (which probably means a hacking attempt?) don't let in
			StringBuffer sb = new StringBuffer("Selected Login Division '");
			sb.append(institutionStNum);
			sb.append("' not configured in KAAJEE for login.");
			throw new KaajeeException(sb.toString());
		}
	}

	/**
	 * Creates a VistaLinkConnection to the specified institution using the supplied Connection Specification.
	 * @param institution institution to get connection for
	 * @param connectionSpec A VistALink connection specification providing reauthentication credentials
	 * @return a VistaLinkConnection to the specified institution using the specified connection spec.
	 * @throws KaajeeException
	 */
	static VistaLinkConnection getVistaLinkConnection(String institution, VistaLinkConnectionSpec connectionSpec)
		throws KaajeeException {
		VistaLinkConnection myConnection = null;
		try {
			String jndiConnectionName =  InstitutionMappingDelegate.getJndiConnectorNameForInstitution(institution);
			InitialContext context = new InitialContext();
			VistaLinkConnectionFactory cf = (VistaLinkConnectionFactory) context.lookup(jndiConnectionName);
			myConnection = (VistaLinkConnection) cf.getConnection(connectionSpec);
			
		} catch (NamingException e) {

			LoginControllerUtils.closeVistaLinkConnection(myConnection);
			StringBuffer sb =
				new StringBuffer("Naming exception; Could not get a connection from connector pool for institution '");
			sb.append(institution);
			sb.append("'.");
			throw new KaajeeException(sb.toString(), e);

		} catch (ResourceException e) {

			LoginControllerUtils.closeVistaLinkConnection(myConnection);
			StringBuffer sb = new StringBuffer("Could not get a connection from connector pool for institution '");
			sb.append(institution);
			sb.append("'.");
			throw new KaajeeException(sb.toString(), e);

		} catch (InstitutionMapNotInitializedException e) {

			StringBuffer sb =
				new StringBuffer("Institution mapping not initialized. Could not get a connection from connector pool for institution '");
			sb.append(institution);
			sb.append("'.");
			throw new KaajeeException(sb.toString(), e);

		} catch (InstitutionMappingNotFoundException e) {

			LoginControllerUtils.closeVistaLinkConnection(myConnection);
			StringBuffer sb = new StringBuffer("Institution Mapping not found for institution '");
			sb.append(institution);
			sb.append("'. Could not get a connection from connector pool for institution.");
			throw new KaajeeException(sb.toString(), e);

		}

		if (logger.isEnabledFor(Level.DEBUG)) {
			logger.debug("got connection.");
		}
		return myConnection;
	}

	/**
	* Caches the user roles.
	* @param jUsername
	* @param myConnection
	* @throws KaajeeException
	*/
	static void cacheUserRoles(String jUsername, VistaLinkConnection myConnection) throws KaajeeException {
		String exceptionMessage = "";
		// TODO have the M role retrieval in a separate method, returning an array list
		// TODO do we still need RoleUtils class if we make this separation?
		try {
			// get role list
			ApplicationRoleListVOSingleton roleListVO = ApplicationRoleListVOSingleton.getInstance();
			// but if not...
			if (roleListVO == null) {
				throw new KaajeeException("Could not retrieve application role list, because ApplicationRoleListVOSingleton has not been initialized.");
			} // cache the roles
			RoleUtils.cacheRoles(jUsername, myConnection, roleListVO);
		} catch (KaajeeException e) {

			LoginControllerUtils.closeVistaLinkConnection(myConnection);
			exceptionMessage = "Could not cache user roles.";
			if (logger.isEnabledFor(Level.ERROR)) {
				logger.error(exceptionMessage, e);
			}
			throw new KaajeeException(exceptionMessage, e);
		}
	}

	/**
	* get a timestamp
	* @return timestamp
	*/
	static Timestamp getTimestamp() {
		// get timestamp
		Calendar cal = Calendar.getInstance();
		long time = cal.getTime().getTime();
		return new java.sql.Timestamp(time);
	}

	/**
	 * Core "jusername" method
	 * @param loginUserInfo
	 * @param prefix
	 * @return
	 */
	static String createJUsername(LoginUserInfoVO loginUserInfo) {
		
		ConfigurationVO contextName = ConfigurationVO.getInstance();
		
		String prefix = contextName.getContextName().substring(1,5);
		
		String parentComputerSystemStationNumber = loginUserInfo.getUserParentComputerSystemStationNumber();
		StringBuffer sbJUsername = new StringBuffer();
		if(prefix != null && prefix != "")
			sbJUsername.append(prefix+"_");
		sbJUsername.append(J_USERNAME_DUZ);
		sbJUsername.append(loginUserInfo.getUserDuz());
		sbJUsername.append(J_USERNAME_COMPSYS);
		sbJUsername.append(parentComputerSystemStationNumber);
		if (logger.isEnabledFor(Level.DEBUG)) {
			logger.debug("created j_username: " + sbJUsername.toString());
		}
		return sbJUsername.toString();

	}

	/**
	 * Create j_password with cactus user prefix
	 * @return j_password
	 */
	static String createJPasswordForCactus() {

		return createJPasswordCore(LoginControllerUtils.J_PASSWORD_PREFIX_CACTUS);
	}

	/**
	 * Create j_password with web user prefix
	 * @return j_password
	 */
	static String createJPasswordForWebUser() {

		return createJPasswordCore(LoginControllerUtils.J_PASSWORD_PREFIX_WEB);
	}
	/**
	 * Create the password to pass to j_security_check
	 * @return the password created to give to j_security_check
	 */
	private static String createJPasswordCore(String prefix) {
		StringBuffer sb = new StringBuffer(prefix);
		sb.append("~");
		sb.append(createOneTimeToken());
		String jPassword = sb.toString();
		if (logger.isEnabledFor(Level.DEBUG)) {
			logger.debug("created j_password:" + jPassword);
		}
		return jPassword;
	}

	/**
	 * Validates an an institution and its computing provider, returning the combined information.
	 * @param divisionStationNumber station number to validate
	 * @return DivisionWithVistaProviderVO object populated with original station# plus station# of facility's
	 * Vista Provider, as returned by the SDS institution utilities.
	 * @throws KaajeeException thrown if 1) the passed division is not in the Institution file, 2) the
	 * passed division doesn't have a Vista Provider identified, 3) the Vista Provider does not have a station
	 * number.
	 * @throws KaajeeInstitutionResourceException if can't get institution information, throw this
	 */
	static DivisionWithVistaProviderVO getValidatedVistaProvider(String divisionStationNumber)
		throws KaajeeInstitutionResourceException, KaajeeException {
		// retrieve Institution for station #
		Institution loginInst = null;
		try {
			loginInst = Institution.factory.obtainByStationNumber(divisionStationNumber);
		} catch (Throwable t) {
			StringBuffer sb =
				new StringBuffer("Institution validation error: Could not retrieve institution from Institution table. ");
			sb.append(t.getMessage());
			// sb.append(t.getStackTrace());
			throw new KaajeeInstitutionResourceException(sb.toString());
		}
		if (loginInst == null) {

			StringBuffer sb =
				new StringBuffer("Institution validation error: Could not find specified division station number in Institution table: '");
			sb.append(divisionStationNumber);
			sb.append("'.");
			throw new KaajeeException(sb.toString());

		} // get Vista Provider for station #
		Institution vistaProviderInst = loginInst.getVistaProvider();
		if (vistaProviderInst == null) {

			StringBuffer sb =
				new StringBuffer("Institution validation error: Could not retrieve Vista Provider institution for specified division station number in Institution table: '");
			sb.append(divisionStationNumber);
			sb.append("'.");
			throw new KaajeeException(sb.toString());

		}
		String vistaProviderInstStNum = vistaProviderInst.getStationNumber();
		// validate Vista Provider for station #
		if ((vistaProviderInstStNum == null) || (vistaProviderInstStNum.length() == 0)) {

			StringBuffer sb =
				new StringBuffer("Institution validation error: Vista Provider institution station number is null in Institution table, for specified division station number: '");
			sb.append(divisionStationNumber);
			sb.append("'.");
			throw new KaajeeException(sb.toString());
		}

		DivisionWithVistaProviderVO returnVal = new DivisionWithVistaProviderVO();
		returnVal.setDivisionStationNumber(divisionStationNumber);
		returnVal.setVistaProviderStationNumber(vistaProviderInstStNum);
		return returnVal;
	}

	/**
	* used to create a one-time token that is used as the j_password value.
	* @return a one-time token
	*/
	static String createOneTimeToken() /* throws UnsupportedEncodingException */ {
		// simplistic one-time token. Could get more involved if judged necessary from a security
		//   standpoint.
		//TODO switch to java.security.SecureRandom --?
		String returnVal;

		try {

			SecureRandom secureRandom = java.security.SecureRandom.getInstance("SHA1PRNG");
			returnVal = String.valueOf(secureRandom.nextLong());

		} catch (NoSuchAlgorithmException e) {

			if (logger.isEnabledFor(Level.DEBUG)) {
				logger.debug(
					"Could not generate password with SHA1PRNG secure random. Switching to standard random generator.");
			}
			Random random = new Random();
			returnVal = String.valueOf(random.nextLong());

		}
		return returnVal;
	}
	
	public static ObjectName getKaajeeAuthenticator(MBeanServer mbs) 
    throws MalformedObjectNameException, NullPointerException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
    	//obtain authenticator for KAAJEE, which should be KaajeeManageableAuthenticator
    	ObjectName kaajeeAuthenticator = null;
    	ObjectName rs = new ObjectName("com.bea:Name=RuntimeService," + 
    	"Type=weblogic.management.mbeanservers.runtime.RuntimeServiceMBean");
    	ObjectName domainMBean = (ObjectName) mbs.getAttribute(rs,
    	"DomainConfiguration");
    	ObjectName securityConfig = (ObjectName) mbs.getAttribute(domainMBean,
    	"SecurityConfiguration");
    	ObjectName defaultRealm = (ObjectName) mbs.getAttribute(securityConfig,
    	"DefaultRealm");            
    	kaajeeAuthenticator = (ObjectName) mbs.invoke(defaultRealm,
    			"lookupAuthenticationProvider",
    			new Object[] {"KaajeeManageableAuthenticator"},
    			new String[] {"java.lang.String"}
    	);
    	return kaajeeAuthenticator;
	}	

	
	static void createAuthenticateUser(String jUsername, String jPassword ) throws KaajeeException  {
        Context ctx = null;
    	try
        {
            ctx           = new InitialContext();
            MBeanServer mbs = (MBeanServer) ctx.lookup("java:comp/env/jmx/runtime");
            ObjectName authenticator = getKaajeeAuthenticator(mbs);

            // remove user, even if it doesn't exist yet
            try {
            	mbs.invoke(authenticator,
            			"removeUser",
            			new Object[] {jUsername},
            			new String[] {"java.lang.String"}
            	);                 		
            }
            catch (Exception ex)
            {
            	logger.error("User not removed: ", ex);
            	//ignore and continue
            }
            // create the user
            mbs.invoke(authenticator,
            		"createUser",
            		new Object[] {jUsername, jPassword, ""},
            		new String[] {"java.lang.String",	"java.lang.String",	"java.lang.String"}
            );                            	
            
        }
    	catch (Exception ex)
    	{
    		logger.error("Error creating user: ", ex);
    		throw new KaajeeException("Error creating user in the KaajeeManageableAuthenticator database");
    	}finally {
            try {
				ctx.close();
			} catch (NamingException e) {
	    		logger.error("Error closing context: ", e);
			}
    	}
    }//createAuthenticateUser

	static void addMemberToGroup(String jUsername, String groupName ) throws KaajeeException {
        Context ctx = null;
    	try
        {
            ctx           = new InitialContext();
            MBeanServer mbs = (MBeanServer) ctx.lookup("java:comp/env/jmx/runtime");
            ObjectName authenticator = getKaajeeAuthenticator(mbs);

            // check existance of group or create it
            boolean groupExists = false;
            
            Boolean retObj = (Boolean) mbs.invoke(authenticator,
            		"groupExists",
            		new Object[] {groupName},
            		new String[] {"java.lang.String"}
            );         
            groupExists = retObj.booleanValue();
            
            if (logger.isEnabledFor(Level.DEBUG))
            {
            	StringBuffer debugMsg = new StringBuffer("group is '");
            	
            	debugMsg.append(groupName);
            	debugMsg.append("'. jUsername is '");
            	debugMsg.append(jUsername);
            	debugMsg.append("'. groupExists is '");
            	debugMsg.append(groupExists);
            	debugMsg.append("'.");
            	logger.debug(debugMsg.toString());
            }
            
            if (!groupExists){
            	// create the group
            	mbs.invoke(authenticator,
            			"createGroup",
            			new Object[] {groupName, ""},
            			new String[] {"java.lang.String", "java.lang.String"}
            	);          
            }
            
            // add member to group 
            mbs.invoke(authenticator,
            		"addMemberToGroup",
            		new Object[] {groupName, jUsername},
            		new String[] {"java.lang.String", "java.lang.String"}
            );                            	
            
        }catch (Exception ex)
    	{
    		logger.error("Error adding user to group: ", ex);
    		throw new KaajeeException("Error adding user to group in KaajeeManageableAuthenticator database");
    	}finally {
            try {
				ctx.close();
			} catch (NamingException e) {
	    		logger.error("Error closing context: ", e);
			}
    	}
            
    }// End addMemberToGroup
	  }
	