package gov.va.med.esr.service.impl;

import gov.va.med.esr.common.batchprocess.AppointmentConversionStatistics;
import gov.va.med.esr.common.model.ee.Application;
import gov.va.med.esr.common.model.ee.VerificationInfo;
import gov.va.med.esr.common.model.lookup.ApplicationMethod;
import gov.va.med.esr.common.model.lookup.ApplicationStatus;
import gov.va.med.esr.common.model.lookup.EnrollmentStatus;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.person.ApplicationInProcess;
import gov.va.med.esr.common.model.person.EnrollmentApplication;
import gov.va.med.esr.common.model.person.FullyQualifiedIdentity;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.SSN;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.common.model.person.id.VPIDEntityKey;
import gov.va.med.esr.common.model.workload.WorkflowCaseInfo;
import gov.va.med.esr.common.persistent.person.ApplicationInProcessDAO;
import gov.va.med.esr.common.persistent.person.EnrollmentApplicationDAO;
import gov.va.med.esr.service.ApplicationInProcessService;
import gov.va.med.esr.service.CommsEmailBulletinService;
import gov.va.med.esr.service.IdmServiceVO;
import gov.va.med.esr.service.MessagingService;
import gov.va.med.esr.service.PersonIdentityTraits;
import gov.va.med.esr.service.trigger.BulletinTrigger;
import gov.va.med.esr.service.trigger.BulletinTriggerEvent;
import gov.va.med.fw.batchprocess.model.JobResult;
import gov.va.med.fw.hl7.InvalidMessageException;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.person.idmgmt.VPID;

import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.SQLException;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.Validate;
import org.hibernate.lob.SerializableClob;

import com.thoughtworks.xstream.XStream;

public class ApplicationInProcessServiceImpl
	extends AbstractRuleAwareServiceImpl
	implements ApplicationInProcessService {

	private static final long serialVersionUID = -5000532063516473278L;

	private ApplicationInProcessDAO applicationInProcessDAO = null;
	private EnrollmentApplicationDAO enrollmentApplicationDAO = null;
	private MessagingService messagingService = null;
	private CommsEmailBulletinService bulletinService = null;

	public ApplicationInProcessServiceImpl() {
		super();
	}


	/**
	 * Looks up a person who is in-process or New Application, by shortVPID, and returns a Person object.
	 * @param shortVPID The shortVPID to lookup
	 * @return A Person object.
	 */
	public Person getInProcessPersonByIcn(String shortVPID) throws ServiceException {

		Validate.notNull(shortVPID, "Person VPID cannot be null");

		Person person = null;
		try {
			SerializableClob xml = this.getEnrollmentApplicationDAO().getInProcessPersonXmlByIcn(shortVPID);

			if (xml != null)
				person = this.deserializeXML(xml);

		} catch (DAOException e) {
			throw new ServiceException("Cannot get Person In Process by shortVPID: " + shortVPID, e);
		} catch (SQLException e) {
			throw new ServiceException("Cannot dezerialize Person in getInProcessPersonByIcn(String shortVPID) by shortVPID: " + shortVPID, e);
		}
		return person;
	}

	public Person getCompletedPersonByIcn(String shortVPID) throws ServiceException {
		Validate.notNull(shortVPID, "Person VPID cannot be null");

		Person person = null;
		try {
			person = this.getApplicationInProcessDAO().getCompletedPersonByIcn(shortVPID);
		} catch (DAOException e) {
			throw new ServiceException("Cannot get AAP Complete Person by shortVPID: " + shortVPID, e);
		}
		return person;
	}

	/**
	 * Looks up an Enrollment Application by shortVPID
	 * @param shortVPID The shortVPID to lookup
	 * @return EnrollmentApplication
	 */
	public EnrollmentApplication getEnrollmentApplicationByIcn(String shortVPID) throws ServiceException
	{
		Validate.notNull(shortVPID, "Person VPID cannot be null");

		try {
			EnrollmentApplication ea= this.getEnrollmentApplicationDAO().getByIcn(shortVPID);

			if (ea == null || ea.getApplicationInProcess() == null)
				return null;

			return ea;
		} catch (DAOException e) {
			throw new ServiceException("Cannot get Application by shortVPID: " + shortVPID, e);
		}
	}
	/**
	 * Looks up an ApplicationInProcess by shortVPID
	 * @param shortVPID The shortVPID to lookup
	 * @return
	 */
	public ApplicationInProcess getByIcn(String shortVPID) throws ServiceException {

		Validate.notNull(shortVPID, "Person VPID cannot be null");

		try {
			EnrollmentApplication ea= this.getEnrollmentApplicationDAO().getByIcn(shortVPID);

			if (ea == null || ea.getApplicationInProcess() == null)
				return null;

			return ea.getApplicationInProcess();
		} catch (DAOException e) {
			throw new ServiceException("Cannot get Application by shortVPID: " + shortVPID, e);
		}
	}

	/**
	 * Get the application statuses for a person with shortVPID (VPID)
	 * @param a short vpid value
	 * @return ArrayList of ApplicationStatus.
	 */
	public List getApplicationStatusesByIcn(String shortVPID) throws ServiceException {

		Validate.notNull(shortVPID, "Person VPID cannot be null");

		List statusList = new ArrayList();
		try {
			List codes= this.getEnrollmentApplicationDAO().getApplicationStatusCodeListByIcn(shortVPID);
			if (codes == null || codes.size() == 0)
				return statusList;

			Iterator it = codes.iterator();

			while (it.hasNext())
			{
				statusList.add(this.getLookupService().getApplicationStatusByCode((String)it.next()));
			}

			return statusList;

		} catch (DAOException e) {
			throw new ServiceException("Cannot get Application Statuses by shortVPID: " + shortVPID, e);
		}
	}

	/**
	 * See if the specified application status exist in the list of Application Statuses
	 * @param applicationStatusList
	 * @param status
	 * @return boolean
	 * @throws ServiceException
	 */
	public boolean existsRecordWithStatus(List applicationStatusList, ApplicationStatus status)  throws ServiceException {
	{
		Validate.notNull(status, "Application Status cannot be null");

		if (applicationStatusList == null || applicationStatusList.size()==0)
			return false;


		String code = status.getCode();
		Iterator it = applicationStatusList.iterator();

		while (it.hasNext())
		{
			if (((ApplicationStatus)it.next()).getCode().equals(code))
				return true;
		}

			return false;
	}

	}
	/**
	 * Looks up a person by ssn and returns a list of Person objects.  This list may be empty.
	 * @param ssn The ssn to lookup
	 * @return A list of Person objects.
	 */
	/*public List getBySsn(String ssn) {
		List list = new ArrayList();

		try {
			list = this.getApplicationInProcessDAO().getBySsn(ssn);
		} catch (Exception e) {
			// do nothing he exception has been logged
		}

		return list;
	}*/

	/**
	 *
	 */




	public void savePersonInProcess(Person person, ApplicationStatus applicationStatus) throws ServiceException {
		Validate.notNull(person, "Person cannot be null");
		Validate.notNull(applicationStatus, "applicationStatus cannot be null");

		EnrollmentApplication ea = null;
		ApplicationInProcess aip = null;
		try {

			ea = this.getEnrollmentApplicationByIcn(person.getVPIDEntityKey().getShortVPID());

			if (ea == null) {
				//not found, brand new
				ea = new EnrollmentApplication();
				aip = new ApplicationInProcess();
				makeEnrollmentApplication(person, ea, applicationStatus);
				makeApplicationInProcess(person, aip);
				aip.setEnrollmentApplication(ea);
				ea.setApplicationInProcess(aip);
			} else {
				// replace the values and update
				aip = ea.getApplicationInProcess();
				makeEnrollmentApplication(person, ea, applicationStatus);
				makeApplicationInProcess(person, aip);
			}

			this.getEnrollmentApplicationDAO().saveObject(ea);

		} catch (DAOException e) {
			throw new ServiceException("Error saving InProcess Person registration.", e);
		}
	}

	/**
	 * Is a person has registration status of "In Process" or "New"
	 * @param ssn The ssn to lookup
	 * @return boolean.
	 */
	 public boolean isInProcess(Person person) throws ServiceException {

		if (person == null) return false;

		Person inProcessPerson = getInProcessPersonByIcn(person.getVPIDEntityKey().getShortVPID());
		return (inProcessPerson == null ? false : true);
	 }

	private void makeEnrollmentApplication(Person person, EnrollmentApplication ea, ApplicationStatus applicationStatus)
	throws ServiceException {

		if (person == null) {
			throw new ServiceException("Save in process received null person object");
		}

		if (ea.getPersonId() == null && person.getPersonEntityKey() != null)
			ea.setPersonId(new BigDecimal(person.getPersonEntityKey().getKeyValueAsString())); //new enrollment application

		PersonIdentityTraits it = person.getIdentityTraits();
		ea.setLastName(it.getLegalName().getFamilyName());
		ea.setFirstName(it.getLegalName().getGivenName());
		ea.setMiddleName(it.getLegalName().getMiddleName());
		//ea.setSigi(it.getSigi());

		// Get the SSN. If getSssnTest() returns nothing then look through the list of SSNs
		// and use the first on that comes back.
        String ssnText = it.getSsnText();

        Set ssns = person.getSsns();
        if ((ssnText == null) && (ssns != null))
        {
            for (Iterator iter = ssns.iterator(); iter.hasNext();)
            {
                SSN ssnObj = (SSN) iter.next();
                // CCR 10471 ADD A PERSON, set the SSN text directly because IdM does not return type
               	ssnText = ssnObj.getSsnText();
            }
        }

		ea.setSsn(ssnText.replaceAll("-", ""));
		ea.setGender(it.getGender());
		ea.setDateOfBirth(it.getBirthRecord().getBirthDate().getDate());
		ea.setIcn(person.getVPIDEntityKey().getShortVPID());
		ea.setApplicationStatus(applicationStatus);
		ea.setSignatureDate(person.getEnrollmentSignatureDate());

		// if the application status is NEW, then default the ApplicationMethod to In Person to avoid not null constraint
		if (ApplicationStatus.NEW_APPLICATION.getCode().equals(applicationStatus.getCode())) {
			ea.setApplicationDate(new Date(System.currentTimeMillis()));
			ea.setApplicationMethod(this.getLookupService().getApplicationMethodByCode(ApplicationMethod.IN_PERSON.getCode()));
		}

	}

	private void makeApplicationInProcess(Person person, ApplicationInProcess aip)
	throws ServiceException {
		try {
			aip.setApplicationContentXML(serializeXML(person));
		} catch (Exception e) {
			throw new ServiceException(e);
		}
	}


	private String serializeXML(Object p){
		String xml = null;

		XStream xstream = new XStream();
		xml = xstream.toXML(p);

		return xml;
	}

	private Person deserializeXML(SerializableClob clob) throws SQLException {

		XStream xstream = new XStream();
		Person p = null;
		InputStream istream = null;
		try {
			istream = clob.getAsciiStream();
			p = (Person)xstream.fromXML(istream);
		} finally {
			if (istream != null) {
				IOUtils.closeQuietly(istream);
			}
		}
		return p;
	}



	public ApplicationInProcessDAO getApplicationInProcessDAO() {
		return applicationInProcessDAO;
	}


	public void setApplicationInProcessDAO(
			ApplicationInProcessDAO applicationInProcessDAO) {
		this.applicationInProcessDAO = applicationInProcessDAO;
	}


	public EnrollmentApplicationDAO getEnrollmentApplicationDAO() {
		return enrollmentApplicationDAO;
	}

	public void setEnrollmentApplicationDAO(
			EnrollmentApplicationDAO enrollmentApplicationDAO) {
		this.enrollmentApplicationDAO = enrollmentApplicationDAO;
	}


	/**
	 * Performs business logic to completeAddPersonRegistration
	 * @param incoming
	 */
	public Person processCompleteRegistration(Person incoming) throws ServiceException {

        String stationNumber = "200ESR";
    	VAFacility sendingFacility = this.getLookupService().getVaFacilityByStationNumber(stationNumber);
    	VerificationInfo info = new VerificationInfo();

			// create workflow case object for potential usage by Rules
			WorkflowCaseInfo caseInfo = new WorkflowCaseInfo();
			//caseInfo.setTransmissionSite(getLookupService().getVaFacilityByStationNumber(sendingFacility);
			caseInfo.setTransmissionSite(sendingFacility);

			// create workflow case object for potential usage by Rules
			info.setWorkflowCaseInfo( caseInfo );

           	// Update person profile for ESR200 Correlation.
			//CCR 13856
	        //getPersonService().updateProfileForESRCorrelation(incoming);
	        getPersonService().updateProfileForAAP(incoming);

	        // Add correlation for the preferred facility
	        FullyQualifiedIdentity identifier = getPersonService().addPreferredFacilityCorrelation(incoming);
	        /* CR13612 – CompletRegistration –begin*/
	        if (identifier !=null && identifier.getStationNumber() != null ) {
			// process the person, determine enrollment, and saves the person data
	        Person onFile = getMessagingService().processAddPerson( incoming, sendingFacility, info );

	        // now our person is processed, unlock the person
	        if (onFile.getEnrollmentDetermination() != null) {
	        	onFile.setPersonLockedReason(null);
	        }

	        // CCR11499 -- ApplicationInProcess serialized person should not be saved after onFile person
	        // has been processed.  It should retain the way the input data was entered (incoming person).

	       	// Update the person's application status
    		savePersonInProcess(incoming,
    				this.getLookupService().getApplicationStatusByCode(
        					ApplicationStatus.COMPLETE.getCode()));

    		// CCR11456 -- send a email bulletin notification to the site to alert them
    		// to the new record
    		sendNotifySitePersonAddedBulletin(onFile, identifier.getIdValue(), identifier.getStationNumber());

    		return onFile;
	        /* CR13612 – CompletRegistration –begin*/
	        }
	        else {
	        	return null;
	        }
	        /* CR13612 – CompletRegistration –end*/
	}

	private void sendNotifySitePersonAddedBulletin(Person person, String dfn, String stationNumber) {
	     try {
	 		Hashtable dataTab = new Hashtable();

	 		// set data for bulletin
			dataTab.put(BulletinTriggerEvent.HecNotifyPersonAddedFields.VPID, person.getVPIDEntityKey().getShortVPID());
			dataTab.put(BulletinTriggerEvent.HecNotifyPersonAddedFields.DFN, dfn);
			dataTab.put(BulletinTriggerEvent.HecNotifyPersonAddedFields.STATION_ID, stationNumber);
			dataTab.put(BulletinTriggerEvent.HecNotifyPersonAddedFields.PREFERRED_FACILITY, person.getMostRecentPreferredFacility().getFacilityName());

			CommsEmailBulletinService emailSrv = this.getBulletinService();
			emailSrv.sendEmailBulletin(BulletinTrigger.DataType.HEC_NOTIFY_SITE_PERSON_ADDED,  dataTab, person);
	     }
	     catch( ServiceException e ) {
	         logger.error("InvalidMessageException", e);
	     }
	}

	/**
	 * For a search result returned from IDM, with no ADR Person table stub, and no in-process record.  Add both.
	 *
	 * @param incoming
	 * @throws ServiceException
	 */
	public void processAddPersonStubAndSaveInProcess(Person incoming) throws ServiceException {

		/* In IDM, not in ADR person table
		We have a person object from IDM search results
		Check and request for ESR 200 correlation
		Create a stub record in Person_Table by ourselves
		Create a stub record in Enrollment _application table
		Allow the user continue with the registration
		*/
		// add ESR Correlation
		getPersonService().addESRCorrelation(incoming);
		// add to ADR Person table
		PersonEntityKey personKey = getPersonService().addPersonStub(incoming.getVPIDEntityKey());
        // Set the personId to be saved in-process
		incoming.setIdentifier(personKey.getKeyValue());
        // Create a stub record in Enrollment _application table
        savePersonInProcess(incoming,
        		this.getLookupService().getApplicationStatusByCode(
				ApplicationStatus.NEW_APPLICATION.getCode()));
	}

	public MessagingService getMessagingService() {
		return messagingService;
	}


	public void setMessagingService(MessagingService messagingService) {
		this.messagingService = messagingService;
	}


	public CommsEmailBulletinService getBulletinService() {
		return bulletinService;
	}


	public void setBulletinService(CommsEmailBulletinService bulletinService) {
		this.bulletinService = bulletinService;
	}



}

