/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/
package gov.va.med.esr.service.impl;

import java.security.SecureRandom;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import org.apache.commons.lang.Validate;

import gov.va.med.fw.model.EntityKey;
import gov.va.med.fw.model.lookup.Lookup;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.security.LDAPService;
import gov.va.med.fw.security.RolePrincipal;
import gov.va.med.fw.security.UserPrincipal;
import gov.va.med.fw.service.ServiceException;

import gov.va.med.esr.common.model.lookup.Capability;
import gov.va.med.esr.common.model.lookup.FunctionalGroup;
import gov.va.med.esr.common.model.security.CapabilitySet;
import gov.va.med.esr.common.model.security.ESRRolePrincipalImpl;
import gov.va.med.esr.common.model.security.ESRUserPrincipalImpl;
import gov.va.med.esr.common.persistent.history.HistoryDAO;
import gov.va.med.esr.common.rule.service.MergeRuleService;

import gov.va.med.esr.service.UserAdminService;

public class UserAdminServiceImpl extends SecurityServiceImpl implements UserAdminService
{
    public static final int CONST_SIGNATURE_CODE_LENGTH = 6;

    private LDAPService ldapService = null;
    private Map historyDAOs;
    private Map rolePermissionMap = null;
    private Map capabilityPermissionMap = null;
    private String ssoEnabled = null;
    private String ssoUsername = null;
    private String ssoLogoffUrl = null;
    private String ssoErrorUrl = null;

    private String encryptionKey = null;
    private String encryptionMethod = null;

    private MergeRuleService mergeRuleService = null;

    public UserAdminServiceImpl() {
        super();
    }

    public String getEncryptionKey() {
        return encryptionKey;
    }

    //CCR 13343 SSOi integration
    public String getSsoEnabled() {
    	return ssoEnabled;
    }

    public void setSsoEnabled(String ssoEnabled) {
    	this.ssoEnabled = ssoEnabled;
    }

    public String getSsoLogoffUrl() {
    	return ssoLogoffUrl;
    }

    public void setSsoLogoffUrl(String ssoLogoffUrl) {
    	this.ssoLogoffUrl = ssoLogoffUrl;
    }

    public String getSsoErrorUrl() {
    	return ssoErrorUrl;
    }

    public void setSsoErrorUrl(String ssoErrorUrl) {
    	this.ssoErrorUrl = ssoErrorUrl;
    }
    
    public String getSsoUsername() {
    	return ssoUsername;
    }

    public void setSsoUsername(String ssoUsername) {
    	this.ssoUsername = ssoUsername;
    }

    public boolean isSsoEnforced() {
    	if (this.ssoEnabled.equalsIgnoreCase("Y")) {
    		return true;
    	}
    	return false;
    }

    public void setEncryptionKey(String encryptionKey) {
        this.encryptionKey = encryptionKey;
    }

    public String getEncryptionMethod() {
        return encryptionMethod;
    }

    public void setEncryptionMethod(String encryptionMethod) {
        this.encryptionMethod = encryptionMethod;
    }

    public LDAPService getLdapService() {
        if (ldapService == null)
            ldapService = (LDAPService) getApplicationContext().getBean("esrLDAPService");
        return ldapService;
    }

    public void setLdapService(LDAPService ldapService) {
        this.ldapService = ldapService;
    }

    public Map getHistoryDAOs() {
        return historyDAOs;
    }

    public void setHistoryDAOs(Map historyDAOs) {
        this.historyDAOs = historyDAOs;
    }

    public Map getCapabilityPermissionMap() {
        return this.capabilityPermissionMap == null ? new HashMap() :
            Collections.unmodifiableMap(this.capabilityPermissionMap);
    }

    public Map getRolePermissionMap() {
        return this.rolePermissionMap == null ? new HashMap():
            Collections.unmodifiableMap(this.rolePermissionMap);
    }

    public void setCapabilityPermissionMap(Map capabilityPermissionMap) {
        this.capabilityPermissionMap = capabilityPermissionMap;
    }

    public void setRolePermissionMap(Map rolePermissionMap) {
        this.rolePermissionMap = rolePermissionMap;
    }

    public MergeRuleService getMergeRuleService() {
        return this.mergeRuleService;
    }

    public void setMergeRuleService(MergeRuleService mergeRuleService) {
        this.mergeRuleService = mergeRuleService;
    }

    public boolean isValidNetworkId(String userName) throws ServiceException
    {
        //Get userinfo from LDAP (Active Directory) server
        return getLDAPUserInfo(userName) != null;
    }

    public ESRUserPrincipalImpl getLDAPUserInfo(String userName) throws ServiceException
    {
        //Get userinfo from LDAP (Active Directory) server
        return (ESRUserPrincipalImpl) getLdapService().getUserInfo(userName);
    }

    public ESRUserPrincipalImpl getUserByName(String userName) throws ServiceException
    {
        return super.getUserByName(userName);
    }

    public String encryptPassword(String clearText) throws ServiceException {
        return getPasswordEncryptionService().encryptPassword(clearText);
    }
    public ESRUserPrincipalImpl updateUserProfile(ESRUserPrincipalImpl user)
            throws ServiceException {
        //set all the required values and update
        try
        {
            // Ensure that something changed for the "update" case.
            if (user.getEntityKey() != null)
            {
                // Get the onFile user
                ESRUserPrincipalImpl onFileUser = (ESRUserPrincipalImpl)getUserById(user.getEntityKey());

                // Ensure that something changed on the incoming entity as compared to the onFile entity
                ensureEntityChanged(user, onFileUser);

                // Evict the on-file entity since we aren't overlaying data onto it
                getSecurityDAO().evict(onFileUser);
            }

            //update user account
            getSecurityDAO().update(user);
        }catch (DAOException ex) {
            throw new ServiceException("User Account Update Failed for user" +
                    user.getName(),ex);
        }
        return user;
    }

    public ESRUserPrincipalImpl updateUserAccount(ESRUserPrincipalImpl user)
        throws ServiceException {
        //set all the required values and update
        try
        {
            // Ensure that something changed for the "update" case.
            if (user.getEntityKey() != null)
            {
                // Get the onFile user
                ESRUserPrincipalImpl onFileUser = (ESRUserPrincipalImpl)getUserById(user.getEntityKey());

                // Ensure that something changed on the incoming entity as compared to the onFile entity
                ensureEntityChanged(user, onFileUser);

                // merge user account information and update
                getMergeRuleService().mergeUserAccount(user, onFileUser);
                getSecurityDAO().update(onFileUser);
            }
            else {
                //update user account
                getSecurityDAO().update(user);
            }
        }catch (DAOException ex) {
            throw new ServiceException("User Account Update Failed for user" +
                    user.getName(),ex);
        }
        return user;
    }

    public ESRUserPrincipalImpl addUserAccount(ESRUserPrincipalImpl user)
            throws ServiceException {

        //generate signature code
        user.setAgreementSignatureCode(generateSignatureAgreementCode());
        user.setSignatureVerified(Boolean.FALSE);

        Date currentDate = getCurrentDate();
        user.setPasswordChangeDate(currentDate);
        user.setPasswordCreateDate(currentDate);

        //Encrypt the password
        user.setPassword(getEncryptedPassword(user.getPassword()));

        return updateUserAccount(user);
    }
    /**
     * Get all the Roles from the Database
     */
    public List getAllRoles() throws ServiceException {
        try{
            return getSecurityDAO().getAllRoles();
        }
        catch (DAOException daoe) {
            throw new ServiceException(" Could not retrive Roles ", daoe);
        }
    }
    /**
     * Get all the CapabilitySets from the Database
     */
    public List getAllCapabilitySets() throws ServiceException {
        try{
            return getSecurityDAO().getAllCapabilitySets();
        }
        catch (DAOException daoe) {
            throw new ServiceException(" Could not retrive CapabilitySets ", daoe);
        }
    }

    /**
     * Get all the Capabilities from the Database
     */
    public List getAllCapabilities() throws ServiceException {
        return getLookupService().findAll(Capability.class);
    }

    public void addCapabilitySet(CapabilitySet capabilitySet) throws ServiceException {
    	updateCapabilitySet(capabilitySet);
    }

    public void updateCapabilitySet(CapabilitySet capabilitySet) throws ServiceException {
        try {
            // Only process updates (i.e. not adds) for entity changed checking
            if (capabilitySet.getEntityKey() != null)
            {
                CapabilitySet onFileCapabilitySet = (CapabilitySet)getCapabilitySetById(capabilitySet.getEntityKey());

                // Ensure that something changed on the incoming entity as compared to the onFile entity
                ensureEntityChanged(capabilitySet, onFileCapabilitySet);

                // Evict the on-file entity since we aren't overlaying data onto it
                getSecurityDAO().evict(onFileCapabilitySet);
            }

            getSecurityDAO().update(capabilitySet);
        }catch (DAOException ex) {
            throw new ServiceException("Update of Capability Set " +
                    capabilitySet.getName() + " Failed" , ex);
        }
    }

    public void addRole(RolePrincipal role) throws ServiceException {
    	updateRole(role);
    }

    public void updateRole(RolePrincipal role) throws ServiceException {
        try
        {
            if (role instanceof ESRRolePrincipalImpl)
            {
                ESRRolePrincipalImpl rolePrincipal = (ESRRolePrincipalImpl)role;

                // Only process updates (i.e. not adds) for entity changed checking
                if (rolePrincipal.getEntityKey() != null)
                {
                    ESRRolePrincipalImpl onFileRolePrincipal = (ESRRolePrincipalImpl)getRoleById(rolePrincipal.getEntityKey());

                    // Ensure that something changed on the incoming entity as compared to the onFile entity
                    ensureEntityChanged(rolePrincipal, onFileRolePrincipal);

                    // Evict the on-file entity since we aren't overlaying data onto it
                    getSecurityDAO().evict(onFileRolePrincipal);
                }
            }

            getSecurityDAO().update(role);
        } catch (DAOException ex)
        {
            throw new ServiceException("Update of Role " + role.getName() + " Failed", ex);
        }
    }

    /**
     * Search for user profile based on the user demographic information
     */
    public ESRUserPrincipalImpl findUser(UserPrincipal user)
        throws ServiceException {
        Validate.notNull(user, "UserPrincipal can not be NULL.");
        try {
            return (ESRUserPrincipalImpl) getSecurityDAO().findUser(user);
        } catch(DAOException ex) {
            throw new ServiceException("Error while searching the user prpfile Reason: ", ex);
        }
    }

    public CapabilitySet getCapabilitySetById(EntityKey entityKey) throws ServiceException {
        return (CapabilitySet)getEntityByEntityKey(entityKey);
    }

    public CapabilitySet getCapabilitySetByName(String name) throws ServiceException {
        try {
            return getSecurityDAO().findCapabilitySetByName(name);
        }catch(DAOException ex) {
            throw new ServiceException("Error while searching for CapabilitySet ", ex);
        }
    }

    public RolePrincipal getRoleById(EntityKey entityKey) throws ServiceException {
        return (RolePrincipal)getEntityByEntityKey(entityKey);
    }

    public RolePrincipal getRoleByName(String name) throws ServiceException {
        try {
            return getSecurityDAO().findRoleByName(name);
        }catch(DAOException ex) {
            throw new ServiceException("Error while searching for Role ", ex);
        }
    }

    public void deleteCapabilitySet(CapabilitySet capabilitySet) throws ServiceException
    {
        delete(capabilitySet.getEntityKey());
    }
    public void deleteRole(RolePrincipal rolePrincipal) throws ServiceException
    {
        delete(rolePrincipal.getEntityKey());
    }

    /**
     * Get ChangeTimes for the entity
     * @param entityKey
     * @return
     * @throws ServiceException
     */
    public Set getHistoryChangeTimes(EntityKey entityKey)
    throws ServiceException {
        try
        {
            HistoryDAO historyDAO = getHistoryDAO(entityKey.getEntityClass().getName());
            return historyDAO.getHistoryChangeTimes(entityKey);
        } catch (DAOException e) {
                throw new ServiceException(e.getMessage(), e);
        }
    }

    /**
    * Get current and previous address by change time
    *
    * @param event
    * @return
    * @throws ServiceException
    */
    public HistoricalInfo getHistoryByChangeTime(ChangeEvent event)
        throws ServiceException {
        try {
            HistoryDAO historyDAO = getHistoryDAO(event.getEntityKey().getEntityClass().getName());
            return historyDAO.getHistoryByChangeTime(event);
        } catch (DAOException e) {
            throw new ServiceException(e.getMessage(), e);
        }
    }

    public List findDeletedRoles() throws ServiceException {
        try {
            HistoryDAO historyDAO = getHistoryDAO(ESRRolePrincipalImpl.class.getName());
            return historyDAO.getDeletedEntities(ESRRolePrincipalImpl.class);
        }catch(DAOException e) {
            throw new ServiceException("Failed to retrieve Deleted Roles", e);
        }
    }

    public List findDeletedCapabilitySets() throws ServiceException{
        try {
            HistoryDAO historyDAO = getHistoryDAO(CapabilitySet.class.getName());
            return historyDAO.getDeletedEntities(CapabilitySet.class);
        }catch(DAOException e) {
            throw new ServiceException("Failed to retrieve Deleted Capability Sets", e);
        }
    }
    /**
     * Get all active users
     */
    public List findAllUsers() throws ServiceException
    {
        try {
            return getSecurityDAO().findAllUsers();
        }catch (DAOException e) {
            throw new ServiceException("Fialed to Retrieve User List",e);
        }
    }

    /**
     * Find users by their role name
     */
    public List findUsersByRoleName(String roleName) throws ServiceException
    {
        try {
            return getSecurityDAO().findUsersByRoleName(roleName);
        }catch (DAOException e) {
            throw new ServiceException("Fialed to Retrieve User List By role name",e);
        }
    }

    public ESRUserPrincipalImpl findUserByName(String userName) throws ServiceException {
	    try {
	        return (ESRUserPrincipalImpl) getSecurityDAO().findUserByUsername(userName);
	    } catch(DAOException ex) {
	        throw new ServiceException("Error while searching the user " + userName + " by userName: ", ex);
	    }
    }

    public List findUsersByCapabilityCode(String capabilityCode) throws ServiceException
    {
        try {
            return getSecurityDAO().findUsersByCapabilityCode(capabilityCode);
        }catch (DAOException e) {
            throw new ServiceException("Fialed to Retrieve User List By Capability name",e);
        }
    }


    /**
     * Get the user by name (lookup interface)
     */
    public Lookup getByCode(String userName) throws ServiceException {
        try {
            return getSecurityDAO().getByCode(userName);
        }catch (DAOException e) {
            throw new ServiceException("Fialed to Retrieve Users List",e);
        }
    }

    /* (non-Javadoc)
	 * @see gov.va.med.esr.service.UserAdminService#getByFullname(java.lang.String)
	 */
	public Lookup getByFullname(String userName) throws ServiceException {
        try {
            return getSecurityDAO().getByFullname(userName);
        }catch (DAOException e) {
            throw new ServiceException("Fialed to Retrieve Users List",e);
        }
	}

    public List findUsersByFunctionalGroup(FunctionalGroup functionalGroup) throws ServiceException {
        try {
            return getSecurityDAO().findUsersByFunctionalGroup(functionalGroup);
        }catch (DAOException e) {
            throw new ServiceException("Fialed to Retrieve Users List",e);
        }
    }

    public List findUsersByRoleAndFunctionalGroup(FunctionalGroup functionalGroup, String roleName) throws ServiceException {
        try {
            return getSecurityDAO().findUsersByRoleAndFunctionalGroup(functionalGroup,roleName);
        }catch (DAOException e) {
            throw new ServiceException("Fialed to Retrieve Users List",e);
        }
    }

	private void delete(EntityKey entityKey) throws ServiceException {
        Validate.notNull(entityKey.getKeyValue(),"Entity Key Value can not be NULL");
        Validate.notNull(entityKey.getEntityClass(),"Entity Class can not be NULL");
        try{
            getSecurityDAO().removeObject(entityKey);
        }catch(DAOException ex) {
            throw new ServiceException("Error while retrieving entity of type: " + entityKey.getEntityClass().getName(), ex);
        }
    }

    private Object getEntityByEntityKey(EntityKey entityKey) throws ServiceException {
        Validate.notNull(entityKey.getKeyValue(),"Entity Key Value can not be NULL");
        Validate.notNull(entityKey.getEntityClass(),"Entity Class can not be NULL");
        try{
            return getSecurityDAO().getByKey(entityKey);
        }catch(DAOException ex) {
            throw new ServiceException("Error while retrieving entity of type: " + entityKey.getEntityClass().getName(), ex);
        }
    }

    private String getEncryptedPassword(String clearTextPassword)
    {        return clearTextPassword;
    }

    protected String generateSignatureAgreementCode() {
        byte[] randomDigits = new byte[CONST_SIGNATURE_CODE_LENGTH];
        //Random generator = new Random();
        SecureRandom secureRandom = new SecureRandom();
        //Generates 6 digits ranging from 0-9
        for (int i=0; i<CONST_SIGNATURE_CODE_LENGTH; i++) {
           //randomDigits[i] = (byte) (generator.nextInt(10) + 48); //conevrt to ASCII char
           randomDigits[i] = (byte) (secureRandom.nextInt(10) + 48); //conevrt to ASCII char
        }
        return new String(randomDigits);
    }

    private HistoryDAO getHistoryDAO(String entityClassName) {
        return (HistoryDAO) getHistoryDAOs().get(entityClassName);
    }
}
