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

import gov.va.med.esr.common.persistent.comms.CommsTemplateDAO;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.Validate;
import org.apache.tools.ant.util.FileUtils;

import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.fw.batchprocess.AbstractDataFileSingleRowIncrementProcess;
import gov.va.med.fw.batchprocess.DataFileProcessExecutionContext;
import gov.va.med.fw.batchprocess.DataProcessExecutionContext;
import gov.va.med.fw.batchprocess.ProcessStatistics;
import gov.va.med.esr.service.CommsLogService;
import gov.va.med.esr.service.CorrespondenceService;
import gov.va.med.esr.service.DemographicService;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.PersonService;
import gov.va.med.esr.common.model.comms.Correspondence;
import gov.va.med.esr.common.model.lookup.ComLetterTemplateType;
import gov.va.med.esr.common.model.lookup.CorrespondenceStatus;
import gov.va.med.esr.common.model.lookup.ComLetterTemplateType.Code;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKey;
import gov.va.med.esr.common.model.person.id.VPIDEntityKey;



/** RTC WI 339894
 * Batch process that reads from incoming CMS address file parsing
 * for CMS successful mailings file returned for ACA 1095B mail requests
 *
 *
 * @author DNS   faulkj
 *
 *
 */
public class ACAMailingResponseAddressFileProcess extends
		AbstractDataFileSingleRowIncrementProcess {

	protected PersonService personService;
	private CorrespondenceService correspondenceService;
	private LookupService lookupService;
	private DemographicService demographicService;
	private CommsLogService commsLogService;
	private CommsTemplateDAO templDAO;
	private String rawData = null;
	ProcessStatistics stats;
	private String outputFileLocation;
	private Writer cpeWriter;
	private int cpeCount = 0;
	File cpeFile;

	/*
	 * @see gov.va.med.fw.batchprocess.AbstractDataProcess#processData(gov.va.med.fw.batchprocess.DataProcessExecutionContext,
	 *      java.util.List)
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public  void processDataRecords(
			      DataFileProcessExecutionContext context, List acquiredData) {

		if(acquiredData.isEmpty()) {
			logger.info("ACA Address File Response Job found no records");
			return;
		}

		if (stats == null ) {
		    stats = context.getProcessStatistics();
		}

		//initialize output file to CPE
		if (cpeWriter == null) {
			cpeFile = new File(this.getOutputFileLocation(), context.getCurrentFile().getName());
			cpeWriter = getOutputWriter(this.getOutputFileLocation(), context.getCurrentFile().getName());
			cpeCount = 0;
		}

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

	     try {
        	Object obj = acquiredData.get(i);

            // if no exception continue reading file data
		    if (processDataRecord(context, obj)) {

			    if (isKeepProcessedData()) {
				  context.getProcessedData().add(obj);
			    } else {
				  updateJobResultsLast(context);
			    }

		    } else {
			  // some error while calling processDataRecord so stop
			  updateJobResultsLast(context);
			  break;
		    }
		  } catch (RuntimeException e) {
				if(logger.isErrorEnabled())
				logger.error("RuntimeException in ACA Address File processDataRecords^"+e.getMessage()+"^"+rawData);
		  }
		}
	}

	//the bene records need to go in separate file that gets brokered by perl script
	private Writer getOutputWriter(String fileLocation, String fileName)
	{
		Writer output = null;
		try {
			File file = new File(fileLocation + fileName);
			output = new BufferedWriter(new FileWriter(file));
		} catch (IOException e) {

		    throw new RuntimeException("ACA Address File Process is unable to generate file to CPE. File= " + fileLocation + fileName+ "Exception: " + e);
		}

		return output;
	}


	@SuppressWarnings("rawtypes")
	protected boolean processDataRecord(DataFileProcessExecutionContext context, Object bean) {

		ACAMailingResponseAddressFileData dataRecord = (ACAMailingResponseAddressFileData) bean;
		boolean success = true;
        this.rawData = context.getCurrentRowRawData();
		try {
			// validate record
			if (dataRecord.validateData().size() > 0) {
				// collect error info
				List invalidData = dataRecord.validateData();
				String errMsg = "";
				for (int i=0;i<invalidData.size();i++) {
					errMsg = errMsg+"^"+(String)invalidData.get(i);
				}
				logger.error("ACA Address File Record Failed - Missing Required Fields: "+errMsg);
				stats.incrementNumberOfErrorRecords();
				return success;
			}

			//put the CPE records into a file in their transfer directory
			//we do nothing with them, they are form 742-801A/D
			if (dataRecord.getFormNumText().contains("801")) {
				synchronized (cpeWriter) {
					cpeWriter.write(dataRecord.getFormNumText() + "^" + dataRecord.getVPID() + "^" + dataRecord.getName() +
							"^" + dataRecord.getAddressline1() + "^" + dataRecord.getAddressline2() + "^" + dataRecord.getCity() + "^" +
							dataRecord.getState() + "^" + dataRecord.getZip() + "^" + dataRecord.getEmailaddress() + "^" + dataRecord.getDatemailed() + "\n");
					cpeWriter.flush();
					cpeCount++;
				}
				context.getProcessStatistics().incrementNumberOfSuccessfulRecords();
				return true;
			}

			try {
				VPIDEntityKey key = CommonEntityKeyFactory.createVPIDEntityKey(dataRecord.getVPID());
				PersonIdEntityKey personkey = this.getPersonService().getPersonIdByVPID(key);

				if (personkey == null) {
					throw new Exception("failed to retrieve person id by VPID in ACA Address File process");
				}

				Code formType;
				if (dataRecord.getFormNumText().contains("800D")) {
					formType = ComLetterTemplateType.FORM_NUMBER_800D;
				} else if (dataRecord.getFormNumText().contains("800A")) {
					formType = ComLetterTemplateType.FORM_NUMBER_800A;
				} else {
					throw new Exception("Unknown Form Type in ACA Address File Process:" + dataRecord.getFormNumText());
				}

				//find the two correspondence entries that are associated to the single mail response
				List<Correspondence> ltrResults = this.getCorrespondenceService().getSentPersonCorrespondence
						(new BigDecimal(personkey.getKeyValueAsString()), formType);
				List<Correspondence> formResults = this.getCorrespondenceService().getSentPersonCorrespondence
						(new BigDecimal(personkey.getKeyValueAsString()), ComLetterTemplateType.FORM_NUMBER_800);

				//no results is a 2015 manual record, insert new letter and 1095B, and set immediate to error status
				if (formResults == null || formResults.isEmpty()) {
					Person person = this.getPersonService().getPersonWithoutIdentityTraits(key);
					if (person != null) {

						//insert a letter of type reported, and a 1095B
						//update right away to error status
						Correspondence ltr = new Correspondence(person);
						DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
						Date date = formatter.parse("2015-01-01");
				    	ltr.setEffectiveDate(date);
						this.getCorrespondenceService().triggerNewCMSCorrespondence(ltr, formType);
						this.getCorrespondenceService().updateACACorrespondence(ltr, CorrespondenceStatus.MAILED_BY_CMS, null);

						Correspondence form = new Correspondence(person);
				    	form.setEffectiveDate(date);
						this.getCorrespondenceService().triggerNewCMSCorrespondence(form, ComLetterTemplateType.FORM_NUMBER_800);
						this.getCorrespondenceService().updateACACorrespondence(form, CorrespondenceStatus.MAILED_BY_CMS, null);

						context.getProcessStatistics().incrementNumberOfSuccessfulRecords();

						return true;
					}
				}

				Correspondence corr = null;
				Correspondence form = null;

				//find the records to update
				try {

					if (ltrResults.size() == 1) {
						corr = ltrResults.get(0);
					} else {
						//more than one in sent status, use the oldest one
						Collections.sort(ltrResults, new Comparator<Correspondence>() {
							  public int compare(Correspondence o1, Correspondence o2) {
							      return o1.getStatusDate().compareTo(o2.getStatusDate());
							  }
							});

						corr = ltrResults.get(0);
					}

					if (formResults.size() == 1) {
						form = formResults.get(0);
					} else {
						//more than one in sent status, use the oldest one
						Collections.sort(formResults, new Comparator<Correspondence>() {
							  public int compare(Correspondence o1, Correspondence o2) {
							      return o1.getStatusDate().compareTo(o2.getStatusDate());
							  }
							});

						form = formResults.get(0);
					}

					//update as error status with returned date as the description
					this.getCorrespondenceService().updateACACorrespondence(corr, CorrespondenceStatus.MAILED_BY_CMS, null);
					this.getCorrespondenceService().updateACACorrespondence(form, CorrespondenceStatus.MAILED_BY_CMS, null);

					context.getProcessStatistics().incrementNumberOfSuccessfulRecords();

				} catch (Exception ex) {
					//put the entry into failed status, so it doesn't stay in 'Sent' status forever is something is wrong with it
					if (corr != null) {
						this.getCorrespondenceService().updateACACorrespondence(corr, CorrespondenceStatus.ENROLLMENT_REJECT, "Error Processing CMS Mailing Response. Mail Sent but Status is Unknown");
					}

					if (form != null) {
						this.getCorrespondenceService().updateACACorrespondence(form, CorrespondenceStatus.ENROLLMENT_REJECT, "Error Processing CMS Mailing Response. Mail Sent but Status is Unknown");
					}

					throw new Exception("Failed to process ACA Address File update", ex);

				}

			} catch (Exception ex) {
				logger.error("Exception in ACA Address File Process " + ex);
				context.getProcessStatistics().incrementNumberOfErrorRecords();
				return true;
			}


		} catch (Exception ex) {
			logger.error("processDataRecord in ACA Address File process- Exception processing data record: " + rawData, ex);
			context.getProcessStatistics().incrementNumberOfErrorRecords();
		}

		return success;
	}

	//flush and close the CPE file
	//remove the file if no records were put there
	protected void handleDataProcessCompleted(DataProcessExecutionContext  context) {
		 try {

			if (cpeWriter != null) {
				cpeWriter.flush();
				cpeWriter.close();
			}
		} catch (Exception ex) {
			logger.error("Unknown Exception in Process ACA Address File:" + ex.getMessage());
		}

		 //remove the cpe file if no records
		 if (cpeCount == 0 && cpeFile != null) {
			 FileUtils.delete(cpeFile);
		 }

	     cpeWriter = null;

	     super.handleDataProcessCompleted(context);
	}

	// update stats last
	private void updateJobResultsLast(DataFileProcessExecutionContext context) {
		  stats.setProcessingEndDate(new Date());
		  updateJobResult(context);
	}

	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		Validate.notNull(personService, "personService is required");
		Validate.notNull(correspondenceService, "correspondenceService is required");
	}

	public PersonService getPersonService() {
		return personService;
	}

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

	public CorrespondenceService getCorrespondenceService() {
		return correspondenceService;
	}

	public void setCorrespondenceService(CorrespondenceService correspondenceService) {
		this.correspondenceService = correspondenceService;
	}

	public LookupService getLookupService() {
		return lookupService;
	}

	public void setLookupService(LookupService lookupService) {
		this.lookupService = lookupService;
	}
	public DemographicService getDemographicService() {
		return demographicService;
	}

	public void setDemographicService(DemographicService demographicService) {
		this.demographicService = demographicService;
	}

	public CommsLogService getCommsLogService() {
		return commsLogService;
	}

	public void setCommsLogService(CommsLogService commsLogService) {
		this.commsLogService = commsLogService;
	}

	public CommsTemplateDAO getTemplDAO() {
		return templDAO;
	}

	public void setTemplDAO(CommsTemplateDAO templDAO) {
		this.templDAO = templDAO;
	}


	public String getOutputFileLocation() {
		return outputFileLocation;
	}


	public void setOutputFileLocation(String outputFileLocation) {
		this.outputFileLocation = outputFileLocation;
	}
}