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

// Java Classes
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// Library Classes
import org.apache.commons.lang.Validate;

// Framework Classes
import gov.va.med.fw.model.EntityKey;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.service.pagination.SearchQueryInfo;
import gov.va.med.fw.util.DateUtils;

// ESR Classes
import gov.va.med.esr.common.model.financials.IncomeTest;
import gov.va.med.esr.common.model.lookup.EligibilityStatus;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.PersonLockedReason;
import gov.va.med.esr.common.model.person.PersonMergeInfo;
import gov.va.med.esr.common.model.person.PersonUnmergeInfo;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKey;
import gov.va.med.esr.common.model.person.id.VPIDEntityKey;
import gov.va.med.esr.common.persistent.person.PersonDAO;
import gov.va.med.esr.common.persistent.person.PersonMergeDAO;
import gov.va.med.esr.common.persistent.person.PersonUnmergeDAO;
import gov.va.med.esr.service.MessagingService;
import gov.va.med.esr.service.PersonIdentityTraits;
import gov.va.med.esr.service.PersonUnmergeService;

/**
 * The person unmerge service implementation.
 *
 * @author DNS   CHENB
 */
public class PersonUnmergeServiceImpl extends AbstractRuleAwareServiceImpl implements PersonUnmergeService
{
    // An instance of serialVersionUID
    private static final long serialVersionUID = -6546285562247188667L;

    /**
     * An instance of personDAO
     */
    private PersonDAO personDAO = null;

    /**
     * An instance of personMergeDAO
     */
    private PersonMergeDAO personMergeDAO = null;

    /**
     * An instance of personUnmergeDAO
     */
    private PersonUnmergeDAO personUnmergeDAO = null;

    private MessagingService messagingService = null;

    /**
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception
    {
        super.afterPropertiesSet();
        Validate.notNull(messagingService, "Missing required messagingService");
        Validate.notNull(personDAO, "Missing required personDAO");
        Validate.notNull(personUnmergeDAO, "Missing required personUnmergeDAO");
    }

    /**
     * Default constructor.
     */
    public PersonUnmergeServiceImpl()
    {
        super();
    }

    /**
     * @see gov.va.med.esr.service.PersonUnmergeService#getPersonUnmergeInfo(gov.va.med.fw.model.EntityKey)
     */
    public PersonUnmergeInfo getPersonUnmergeInfo(EntityKey personUnmergeInfoEntityKey) throws ServiceException
    {
        // Get the unerge info
        PersonUnmergeInfo info = null;
        try
        {
            info = (PersonUnmergeInfo)personUnmergeDAO.getByKey(personUnmergeInfoEntityKey);
        }
        catch (DAOException ex)
        {
            throw new ServiceException("Failed to get PersonUnmergeInfo for: " + personUnmergeInfoEntityKey.toString(),
                ex);
        }

        // Fill in the identity trait information
        if (info != null)
        {
            fillPersonIdentityTraits(info);
        }

        // Return the unmerge info
        return info;
    }

    /**
     * Process and store PersonUnmerge request from external. If there is a record in Person_Merge with matching pair
     * of pesonIds and not completed, then delete it and not insert anything to Person_Unmerge. If there is a record in
     * Person_Unmerge for both PersonIds, then do nothing.
     * <p>Note that this may or may not necessarily mean an undo of a "merge".</p>
     *
     * @param info the person merge info
     *
     * @see gov.va.med.esr.service.PersonUnmergeService#processPersonUnmergeInfo(gov.va.med.esr.common.model.person.PersonUnmergeInfo)
     */
    public void processPersonUnmergeInfo(PersonUnmergeInfo info) throws ServiceException
    {
        // Get the latest data from the site to aid in going the unmerge
        querySiteForFreshData(info);

        try
        {
            // If a person merge info exists and isn't completed, then delete it
            PersonMergeInfo existInfo = personMergeDAO.findByPersonIds(
                info.getPerson1EntityKey(), info.getPerson2EntityKey());
            if ((existInfo != null) && (!existInfo.isMergeCompleted()))
            {
                personMergeDAO.removeObject(existInfo.getEntityKey());
                return;
            }

            // See if an unmerge record already exists
            PersonUnmergeInfo existUnmergeInfo = personUnmergeDAO.findByPersonIds(
                info.getPerson1EntityKey(), info.getPerson2EntityKey());

            // If an unmerge record exists already, just return
            if (existUnmergeInfo != null)
            {
                return;
            }

            // Update the unmerge info
            personUnmergeDAO.saveObject(info);
        }
        catch (DAOException ex)
        {
            throw new ServiceException("Failed to save PersonUnmergeInfo: " + info.toString(), ex);
        }
    }

    /**
     * Query a site for updated data.
     * @param info The person unmerge info
     * @throws ServiceException if any problems were encountered
     */
    private void querySiteForFreshData(PersonUnmergeInfo info) throws ServiceException
    {
        // 1) determine if toVpid has EE data...if so, wipe it out and save
        PersonEntityKey key = info.getToPersonEntityKey();
        Person person = getPersonService().getPerson(key);
        PersonLockedReason lockedReasonObj = person.getPersonLockedReason();
        String lockedReason = lockedReasonObj != null ? lockedReasonObj.getReason() : null;
        // as long as this person has EE data, wipe him clean
        if (!PersonLockedReason.PERSON_NO_ENROLLMENT_DETERMINATION.getReason().equals(lockedReason))
        {
            if (logger.isInfoEnabled())
                logger.info(
                    "Due to external MOVE event and the need to start over with site data, ESR is clearing out E&E data for Person [" +
                        key.getKeyValueAsString() + "]");
            // allows for future triggering of a QRYZ11 (when we receive the ORFZ07)
            person.getEnrollmentDetermination().setPerson(null);
            person.setEnrollmentDetermination(null);
            // allows for all incoming site data to be accepted (when we receive the ORFZ07)
            if (person.getEligibilityVerification() != null)
            {
                person.getEligibilityVerification().setEligibilityStatus(
                    getLookupService().getEligibilityStatusByCode(
                        EligibilityStatus.CODE_PENDING_VERIFICATION.getCode()));
                person.getEligibilityVerification().setAacIndicator(null);
            }
            getPersonService().save(person, true);
        }
        // 2) query site with QRYZ07 (async event that is handled after this TX is committed via TriggerEvent design)
        IncomeTest current = getHelperService().getCurrentIncomeTest(person);
        Calendar cal = Calendar.getInstance();
        cal.setTime(DateUtils.getCurrentDate()); // default: current year - 1
        cal.add(Calendar.YEAR, -1);
        Integer incomeYear = current != null ? current.getIncomeYear() : new Integer(cal.get(Calendar.YEAR));
        getMessagingService().triggerQRYZ07(
            person,
            getLookupService().getVaFacilityByStationNumber(info.getStationNumber()),
            incomeYear,
            true);
        // 3) (async) receive ORFZ07 and overlay all data onto toVpid
    }

    /**
     * Update the identity traits for the person unmerge info
     * @param info The person unmerge info
     * @throws ServiceException if any problems were encountered
     */
    private void fillPersonIdentityTraits(PersonUnmergeInfo info) throws ServiceException
    {
        try
        {
            PersonIdEntityKey key1 = info.getPerson1EntityKey();
            VPIDEntityKey vpid1 = personDAO.getVPIDByPersonId(key1);
            info.setPerson1IdentityTraits(this.getPsDelegateService().getIdentityTraits(vpid1));

            PersonIdEntityKey key2 = info.getPerson2EntityKey();
            VPIDEntityKey vpid2 = personDAO.getVPIDByPersonId(key2);
            info.setPerson2IdentityTraits(this.getPsDelegateService().getIdentityTraits(vpid2));
        }
        catch (DAOException e)
        {
            throw new ServiceException("Failed to convert PersinIDEntityKey with VPIDEntityKey: " + info.toString(), e);
        }
    }

    /**
     * @see gov.va.med.esr.service.PersonUnmergeService#deletePersonUnmergeInfo(gov.va.med.esr.common.model.person.PersonUnmergeInfo)
     */
    public void deletePersonUnmergeInfo(PersonUnmergeInfo info) throws ServiceException
    {
        try
        {
            personUnmergeDAO.removeObject(info.getEntityKey());
        }
        catch (DAOException e)
        {
            throw new ServiceException("Failed to delete PersonUnmergeInfo: " + info.toString(), e);
        }
    }

    /**
     * Searches and returns a list of PersonMergeInfo objects.
     *
     * @param searchQueryInfo The search criteria
     *
     * @return The list of PersonMergeInfo objects
     * @throws ServiceException if any problems were encountered.
     */
    public List search(SearchQueryInfo searchQueryInfo) throws ServiceException
    {
        List results = null;
        try
        {
            results = personUnmergeDAO.loadAll(PersonUnmergeInfo.class);
            if (results == null)
            {
                results = new ArrayList();
            }
        }
        catch (DAOException ex)
        {
            throw new ServiceException("Failed to loadAll PersonUnmergeInfo objects from the DAO.", ex);
        }

        searchQueryInfo.setTotalNumberOfEntries(results.size());
        HashMap personIdMap = new HashMap();
        for (int i = 0; i < results.size(); i++)
        {
            Object obj = results.get(i);
            if (obj != null && (obj instanceof PersonUnmergeInfo))
            {
                PersonUnmergeInfo info = (PersonUnmergeInfo)obj;
                PersonIdEntityKey personId1 = info.getPerson1EntityKey();
                PersonIdEntityKey personId2 = info.getPerson2EntityKey();
                try
                {
                    VPIDEntityKey vpid1 = personDAO.getVPIDByPersonId(personId1);
                    VPIDEntityKey vpid2 = personDAO.getVPIDByPersonId(personId2);
                    personIdMap.put(personId1, vpid1);
                    personIdMap.put(personId2, vpid2);
                }
                catch (DAOException ex)
                {
                    throw new ServiceException("Failed to getVPIDByPersonId from the DAO.", ex);
                }
            }
            //calling psDelegateService one by one is expensive
            //this.fillPersonIdentityTraits((PersonUnmergeInfo)results.get(i));
        }
        Map traits = this.getPsDelegateService().getIdentityTraits(personIdMap.values());
        for (int i = 0; i < results.size(); i++)
        {
            Object obj = results.get(i);
            if (obj != null && (obj instanceof PersonUnmergeInfo))
            {
                PersonUnmergeInfo info = (PersonUnmergeInfo)obj;
                PersonIdEntityKey personId1 = info.getPerson1EntityKey();
                PersonIdEntityKey personId2 = info.getPerson2EntityKey();
                VPIDEntityKey vpid1 = (VPIDEntityKey)personIdMap.get(personId1);
                VPIDEntityKey vpid2 = (VPIDEntityKey)personIdMap.get(personId2);
                info.setPerson1IdentityTraits((PersonIdentityTraits)traits.get(vpid1.getVPID()));
                info.setPerson2IdentityTraits((PersonIdentityTraits)traits.get(vpid2.getVPID()));
            }
        }

        // Set resultant information to the caller per the PaginatedSearchService contract
        searchQueryInfo.setSearchTypePerformed(SearchQueryInfo.SEARCH_READ_ALL);
        searchQueryInfo.setSortPerformed(false);
        searchQueryInfo.setSortColumnNotSupported(false);

        return results;
    }

    /**
     * @return Returns the personMergeDAO.
     */
    public PersonDAO getPersonDAO()
    {
        return personDAO;
    }

    /**
     * @param personDAO The personMergeDAO to set.
     */
    public void setPersonDAO(PersonDAO personDAO)
    {
        this.personDAO = personDAO;
    }

    /**
     * @return Returns the personMergeDAO.
     */
    public PersonMergeDAO getPersonMergeDAO()
    {
        return personMergeDAO;
    }

    /**
     * @param personMergeDAO The personMergeDAO to set.
     */
    public void setPersonMergeDAO(PersonMergeDAO personMergeDAO)
    {
        this.personMergeDAO = personMergeDAO;
    }

    /**
     * @return Returns the personUnmergeDAO.
     */
    public PersonUnmergeDAO getPersonUnmergeDAO()
    {
        return personUnmergeDAO;
    }

    /**
     * @param personUnmergeDAO The personUnmergeDAO to set.
     */
    public void setPersonUnmergeDAO(PersonUnmergeDAO personUnmergeDAO)
    {
        this.personUnmergeDAO = personUnmergeDAO;
    }

    /**
     * @return Returns the messagingService.
     */
    public MessagingService getMessagingService()
    {
        return messagingService;
    }

    /**
     * @param messagingService The messagingService to set.
     */
    public void setMessagingService(MessagingService messagingService)
    {
        this.messagingService = messagingService;
    }
}
