package gov.va.med.esr.common.batchprocess;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import gov.va.med.esr.common.model.financials.PatientVisitSummary;
import gov.va.med.esr.common.model.lookup.ComLetterTemplateType;
import gov.va.med.esr.common.model.lookup.SourceDesignation;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.lookup.VAFacilityType;
import gov.va.med.esr.common.model.messaging.SiteIdentity;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.PreferredFacility;
import gov.va.med.esr.common.model.person.id.PersonEntityKey;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKeyImpl;
import gov.va.med.esr.service.DuplicatePrintRequestServiceException;
import gov.va.med.esr.service.HandBookService;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.PSDelegateService;
import gov.va.med.esr.service.PersonService;
import gov.va.med.esr.service.trigger.LetterTrigger;
import gov.va.med.esr.service.trigger.LetterTriggerIdentity;
import gov.va.med.fw.batchprocess.AbstractDataQueryProcess;
import gov.va.med.fw.batchprocess.DataProcessExecutionContext;

/**
 * CCR 14068
 * Batch process to populate PF for on-file records with no PF on file
 * Created June 15, 2015 for ES 4.2.1 maintenance release
 * @author Josh Faulkner
 * @version 1.0
 */

public class PreferredFacilityCleanupProcess extends AbstractDataQueryProcess {

	private PSDelegateService psDelegateService = null;
	private PersonService personService = null;
	private LookupService lookupService = null;
	private HandBookService handBookService = null;
	private static final int INCREMENT_SIZE = 25;

	protected void processData(DataProcessExecutionContext context, List acquiredData) {

        if (logger.isInfoEnabled())
            logger.info("PF Cleanup Process : Query result Size="+(acquiredData == null ? 0 : acquiredData.size()));

        if (acquiredData == null)
            return;

        for (int i = 0; i < acquiredData.size(); i++) {

        	BigDecimal personId = (BigDecimal) acquiredData.get(i);

        	PersonEntityKey key = new PersonIdEntityKeyImpl(personId);

        	try {

        		Person person  = this.getPersonService().getPerson(key);

        		//make sure PF wasn't assigned while job was running, first one will take a very long time to complete
        		if (person.getPreferredFacilities() == null || person.getPreferredFacilities().isEmpty()) {

        			//get all correlations, look only for medical treating type
        			Set<?> sites = this.getPsDelegateService().getSites(person.getVPIDEntityKey());
		            List<SiteIdentity> medFacilities = new ArrayList<SiteIdentity>();

		            if (sites != null && !sites.isEmpty()) {

		            	for( Iterator<?> iter = sites.iterator(); iter.hasNext(); ) {

		            		SiteIdentity siteIdentity = (SiteIdentity) iter.next();
		            		if (siteIdentity.getVaFacility().getType().getIsMedicalTreating()) {
			            		medFacilities.add(siteIdentity);
			            	}
		            	}
		            }

		            //if only one medical site correlation then assign that as the PF
		            if (medFacilities.size() == 1) {
		            	savePreferredFacility(person, ((SiteIdentity)medFacilities.get(0)).getVaFacility(), context);

		            } else {
		            	//try to set PF to most recent visited facility if not exactly one site is known
		            	Map pvs = person.getPatientVisitSummaries();

		            	if (pvs != null && !pvs.isEmpty()) {
		            		List<PatientVisitSummary> sortedPatientVisitSummaries = sortLastVisitDateRecentToOld(pvs.values());
		            		VAFacility recent = getPreferredFacilityRecentVisit(sortedPatientVisitSummaries);
		            		savePreferredFacility(person, recent, context);
		            	} else if (medFacilities.size() > 1) {
		            		//per CR, if multiple correlations but no visit info, user nearest logical zip
		            		VAFacility closest = findNearestLogicalZip(person, medFacilities);
		            		if (closest != null) {
		            			savePreferredFacility(person, closest, context);
		            		} else {
		            			throw new Exception("Nearest facility cannot be determined");
		            		}
		            	} else {
		            		//cannot determine any facility, log as error
		            		context.getProcessStatistics().incrementNumberOfErrorRecords();
		            		logger.error("Error in PF cleanup Process, cannot assign PF to Person " + personId);
		            	}
		            }

        		} else {
                    logger.info("PF already assigned. Skipping acquired data for person: " + personId);
        			context.getProcessStatistics().incrementNumberOfSuccessfulRecords();
        		}
            } catch (Exception ex) {

            	context.getProcessStatistics().incrementNumberOfErrorRecords();
                String errMsg = "UNK Error while executing PF cleanup process for person " + personId;
                context.getExceptionData().add(errMsg+ " Reason: "+ ex.getMessage());

                logger.error(errMsg + " Reason: ", ex);

            } finally {
            	//update job results incrementally
	    		if (context.getProcessStatistics().isTotalNumberMod(INCREMENT_SIZE))
	    			this.updateJobResult(context);
            }
        }
    }

    public PersonService getPersonService() {
    	return personService;
    }

    public void setPersonService(PersonService personService) {
    	this.personService = personService;
    }

    public LookupService getLookupService() {
    	return lookupService;
    }

    public void setLookupService(LookupService lookupService) {
    	this.lookupService = lookupService;
    }

    public PSDelegateService getPsDelegateService() {
		return psDelegateService;
	}
	public void setPsDelegateService(PSDelegateService psDelegateService) {
		this.psDelegateService = psDelegateService;
	}

	public HandBookService getHandBookService() {
		return handBookService;
	}

	public void setHandBookService (HandBookService handBookService) {
		this.handBookService = handBookService;
	}

	private VAFacility findNearestLogicalZip(Person person, List medFacilities) {

		if (person == null || person.getPermanentAddress() == null)
			return null;

		String zip = person.getPermanentAddress().getZipCode();

		if (zip == null)
			return null;

		List facilities = new ArrayList();

		for( Iterator<?> iter = medFacilities.iterator(); iter.hasNext(); ) {
    		SiteIdentity siteIdentity = (SiteIdentity) iter.next();
    		facilities.add(siteIdentity.getVaFacility());
    	}

		boolean match = false;
		int level = 5;
		VAFacility nearest = null;

		//walk back to single digit if needed
		for (int i = 0; i < 5; i++) {
			if (match) break;

			zip = zip.substring(0, level);

			Iterator itr = facilities.iterator();

			//look for zip match on current level for any OPC, VAMC or CBOC that is active
			while (itr != null && itr.hasNext()) {
				VAFacility vamc = (VAFacility)itr.next();
				if (vamc != null &&  vamc.getEndDate() == null) {
					if (vamc.getStreetAddress() != null && vamc.getStreetAddress().getZipCode() != null) {
						String vaZip = vamc.getStreetAddress().getZipCode().substring(0, level);
						if (zip.equals(vaZip)) {
							match = true;
							nearest = vamc;
							break;
						}
					}
				}
			}
			level--;
		}

		if (nearest == null) {
			nearest = (VAFacility)facilities.get(0);
		}

		return nearest;
	}

	/**
	 * Saves calculated facility to person
	 * @param person Person
	 * @param facility VAFacility to add
	 */
	private void savePreferredFacility(Person person, VAFacility facility, DataProcessExecutionContext context) {
		try {
			PreferredFacility pf = new PreferredFacility();
			List conditions = null;

			ComLetterTemplateType type = this.getLookupService()
					.getComLetterTemplateTypeByCode(
							ComLetterTemplateType.FORM_NUMBER_400H
							.getCode());

			pf.setAssignmentDate(new Date());
			pf.setFacility(facility);
			pf.setSourceDesignation(this.getLookupService().getSourceDesignationByCode(SourceDesignation.CODE_ESR.getCode()));

			person.addPreferredFacility(pf);

			this.getPersonService().save(person);
			context.getProcessStatistics().incrementNumberOfSuccessfulRecords();


        	conditions = getHandBookService().requestHandBook(person.getEntityKey().getKeyValueAsString(), new LetterTriggerIdentity(type, LetterTrigger.CategoryType.VETERAN_LETTER));


		} catch (Exception ex) {
			context.getProcessStatistics().incrementNumberOfErrorRecords();
			logger.error("Error saving preferred facility during PF cleanup job for person " + ex);
		}
	}

	/**
	 * Sorts the PatientVisitSummary on last visit date from recent to old.
	 * @param patientVisitSummaries PatientVisitSummary objects
	 * @return sorted PatientVisitSummary on last visit date from recent to old.
	 */
	private List<PatientVisitSummary> sortLastVisitDateRecentToOld(Collection<PatientVisitSummary> patientVisitSummaries)
    {
	    List<PatientVisitSummary> sortedPatientVisitSummaries = new ArrayList();
        if(patientVisitSummaries != null && !patientVisitSummaries.isEmpty()) {

            for( Iterator iter = patientVisitSummaries.iterator(); iter.hasNext(); ) {
            	PatientVisitSummary summary = (PatientVisitSummary) iter.next();
            	if (summary.getLastVisitDate() != null) {
            		sortedPatientVisitSummaries.add(summary);
            	}
            }

            Comparator comparator = new Comparator() {
                public int compare(Object pObject1, Object pObject2) {
                    Date date1 = (pObject1 instanceof PatientVisitSummary) ? ((PatientVisitSummary)pObject1).getLastVisitDate() : null;
                    Date date2 = (pObject2 instanceof PatientVisitSummary) ? ((PatientVisitSummary)pObject2).getLastVisitDate() : null;
                    return (date1 != null && date2 != null) ? (-date1.compareTo(date2)) : 0;
                }
            };
            Collections.sort(sortedPatientVisitSummaries,comparator);
        }
        return sortedPatientVisitSummaries;
    }


	/**
	 * Finds the most recent facility providing care
	 * @param sortedPatientVisitSummaries
	 */
	private VAFacility getPreferredFacilityRecentVisit(Collection<PatientVisitSummary> sortedPatientVisitSummaries)
	{
	    PatientVisitSummary PVS = null;
	    if(sortedPatientVisitSummaries != null && !sortedPatientVisitSummaries.isEmpty()) {
	        PVS = (PatientVisitSummary)sortedPatientVisitSummaries.iterator().next();
	    }
	    return (PVS != null) ? PVS.getFacilityVisited() : null;
	}
}
