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

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.lang.Validate;

import gov.va.med.esr.common.model.comms.CmsErrorFileEntry;
import gov.va.med.esr.common.model.comms.CmsMailedFileEntry;
import gov.va.med.esr.common.model.comms.CmsRejectFileEntry;
import gov.va.med.esr.common.model.comms.CmsReturnedByUSPSFileEntry;
import gov.va.med.esr.common.model.comms.CommsImportStatistics;
import gov.va.med.esr.common.model.comms.CommsLogEntry;
import gov.va.med.esr.common.model.comms.CommsTransByFormNumber;
import gov.va.med.esr.common.model.comms.CommsTransLog;
import gov.va.med.esr.common.model.comms.ErrorFileEntry;
import gov.va.med.esr.common.model.comms.MailedFileEntry;
import gov.va.med.esr.common.model.comms.RejectFileEntry;
import gov.va.med.esr.common.model.lookup.ComMailingStatusType;
import gov.va.med.esr.common.model.lookup.MessageType;
import gov.va.med.esr.common.model.person.PersonChangeLogEntry;
import gov.va.med.esr.common.persistent.comms.CommsLogEntryDAO;
import gov.va.med.esr.common.persistent.comms.CommsTransLogDAO;
import gov.va.med.esr.common.persistent.comms.hibernate.ImportFileDAOImpl;
import gov.va.med.esr.common.persistent.person.PersonSubmittedAdvice;
import gov.va.med.esr.service.CommsLogService;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.impl.AbstractRuleAwareServiceImpl;
import gov.va.med.fw.model.EntityKey;
import gov.va.med.fw.model.EntityKeyFactory;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.service.ServiceException;

public class CmsImportProcess extends AbstractRuleAwareServiceImpl {
	/*
	 * Some static variables
	 */
	// transmission log type codes
	private static final String IMPORT_TRANS_REJECT_TYPE_CODE = "COMREJ-E";
	private static final String IMPORT_TRANS_ERROR_TYPE_CODE = "COMERR-E";
	private static final String IMPORT_TRANS_MAILED_TYPE_CODE = "COMADD-E";

	// transmission log types
	private static MessageType IMPORT_TRANSMISSION_REJECT_TYPE = null;
	private static MessageType IMPORT_TRANSMISSION_ERROR_TYPE = null;
	private static MessageType IMPORT_TRANSMISSION_MAILED_TYPE = null;

	// Support Final letters for CMS
	private static ComMailingStatusType REJECT_BY_CMS_STATUS = null;
	private static ComMailingStatusType ERROR_BY_CMS_STATUS = null;
	private static ComMailingStatusType MAILED_BY_CMS_STATUS = null;
	private static ComMailingStatusType RETURNED_BY_POST_OFFICE_STATUS = null;

	// REMAIL STATUS
	private static final String REMAILED_INDICATOR = "REMAILED";

	private static final String ERROR_REASONS_SEPERATOR = "^";

	private LookupService lookupService = null;

	private CommsTransLogDAO transDAO = null;
	private CommsLogEntryDAO commsLogDAO = null;
	private ImportFileDAOImpl fileDAO = null;

	private CommsLogService commsLogService;

	private void init() throws ServiceException {
		if (lookupService == null) {
			lookupService = (LookupService) getComponent("lookupService");

			// transmission type
			IMPORT_TRANSMISSION_REJECT_TYPE = lookupService
					.getMessageTypeByCode(IMPORT_TRANS_REJECT_TYPE_CODE);
			IMPORT_TRANSMISSION_ERROR_TYPE = lookupService
					.getMessageTypeByCode(IMPORT_TRANS_ERROR_TYPE_CODE);
			IMPORT_TRANSMISSION_MAILED_TYPE = lookupService
					.getMessageTypeByCode(IMPORT_TRANS_MAILED_TYPE_CODE);

			// mailing status
			REJECT_BY_CMS_STATUS = lookupService
					.getComMailingStatusTypeByCode(ComMailingStatusType.REJECT_BY_CMS
							.getCode());
			ERROR_BY_CMS_STATUS = lookupService
					.getComMailingStatusTypeByCode(ComMailingStatusType.ERROR_BY_CMS
							.getCode());
			MAILED_BY_CMS_STATUS = lookupService
					.getComMailingStatusTypeByCode(ComMailingStatusType.MAILED_BY_CMS
							.getCode());
			RETURNED_BY_POST_OFFICE_STATUS = lookupService
					.getComMailingStatusTypeByCode(ComMailingStatusType.RETURN_BY_POST_OFFICE
							.getCode());
		}
	}

	public void afterPropertiesSet() throws Exception {
		super.afterPropertiesSet();

		Validate.notNull(this.commsLogDAO,
				"A Comms Log Entry DAO must be configured");
		Validate.notNull(this.transDAO,
				"An Comms Transmission Log DAO must be configured");
		Validate.notNull(this.fileDAO,
				"An Comms Import File DAO must be configured");
		Validate.notNull(this.commsLogService, "commsLogService is required");
	}

	public CommsImportStatistics execute(String[] args) throws Exception {
		init();

		CommsImportStatistics stat = new CommsImportStatistics();
		stat.setProcessStartTime(new Date());
		// Set changeLogs = new HashSet();

		// read then process error file entries
		stat.setErrorFileProcessStartTime(new Date());
		// CCR - IVM Final Letter support
		try {
			processCmsErrorFile(stat);
		} catch (Exception ex1) {
			logger.error("Failed to process CMS import error file", ex1);
		}
		stat.setErrorFileProcessEndTime(new Date());

		// read then process reject file entries
		stat.setRejectFileProcessStartTime(new Date());
		// CCR - IVM Final Letter support
		try {
			processCmsRejectFile(stat);
		} catch (Exception ex2) {
			logger.error("Failed to process CMS import rejected file", ex2);
		}
		stat.setRejectFileProcessEndTime(new Date());

		// read then process mailed file entries
		stat.setMailedFileProcessStartTime(new Date());
		// CCR - IVM Final Letter support
		try {
			processCmsMailedFile(stat);
		} catch (Exception ex3) {
			logger.error("Failed to process CMS import mailed file", ex3);
		}

		stat.setMailedFileProcessEndTime(new Date());

		// CCR - IVM Final Letter support
		try {
			processCmsReturnedMailFile(stat);
		} catch (Exception ex3) {
			logger.error("Failed to process CMS returned mail file", ex3);
		}

		// TODO: update workload for error files
		// updateWorkload(errorFileList);

		stat.setProcessEndTime(new Date());
		try {
			updateCommsTransLog(stat);
			// if (changeLogs!=null & changeLogs.size()>0)
			// getPersonService().logChanges(changeLogs);
		} catch (Exception ex) {
			logger.error("Failed to update comms trans log");
		}
		return stat;
	}

	/**
	 * @return Returns the commsLogDAO.
	 */
	public CommsLogEntryDAO getCommsLogDAO() {
		return commsLogDAO;
	}

	/**
	 * @param commsLogDAO
	 *            The commsLogDAO to set.
	 */
	public void setCommsLogDAO(CommsLogEntryDAO commsLogDAO) {
		this.commsLogDAO = commsLogDAO;
	}

	/**
	 * @return Returns the fileDAO.
	 */
	public ImportFileDAOImpl getFileDAO() {
		return fileDAO;
	}

	/**
	 * @param fileDAO
	 *            The fileDAO to set.
	 */
	public void setFileDAO(ImportFileDAOImpl fileDAO) {
		this.fileDAO = fileDAO;
	}

	/**
	 * @return Returns the transDAO.
	 */
	public CommsTransLogDAO getTransDAO() {
		return transDAO;
	}

	/**
	 * @param transDAO
	 *            The transDAO to set.
	 */
	public void setTransDAO(CommsTransLogDAO transDAO) {
		this.transDAO = transDAO;
	}

	// This is done so that DataSync for HEC Legacy can send back the status of
	// the mailing response
	private PersonChangeLogEntry createPersonChangeLogEntry(CommsLogEntry cle) {
		PersonChangeLogEntry p = new PersonChangeLogEntry();
		p.setPersonEntityKey(cle.getPersonIdEntityKey());
		p
				.setSubmitterDescription(PersonSubmittedAdvice.SUBMITTER_DATA_DESTINED_FOR_HECLEGACYDATASYNC);
		return p;
	}




	private void processCmsMailedFile(CommsImportStatistics stat)
			throws Exception {
		ArrayList cmsMailedFileList = fileDAO.readCmsMailedFile(stat);
		if (cmsMailedFileList == null || cmsMailedFileList.isEmpty())
			return; // changeLogs;

		Set changeLogs = new HashSet();
		CommsLogEntry log = null;
		CmsMailedFileEntry entry = null;

		for (int i = 0; i < cmsMailedFileList.size(); i++) {
			entry = (CmsMailedFileEntry) cmsMailedFileList.get(i);
			EntityKey key = EntityKeyFactory.createEntityKey(new BigDecimal(
					entry.getCommunicationId()), CommsLogEntry.class);
			log = (CommsLogEntry) commsLogDAO.getByKey(key);

			if (log != null) {
				try {
					// update log
					log.setSentToIVM(Boolean.TRUE);
					log.addMailingStatus(MAILED_BY_CMS_STATUS);
					log.setMailingDate(entry.getMailedDate());
					if (log.getRemailIndicator() != null)
						log.setRemailIndicator(REMAILED_INDICATOR);

					commsLogDAO.update(log);

					// update statistics
					stat.updateCmsMailedStatistics(entry);

					// CCR9192 new - update log for each log entry
					changeLogs.clear();
					changeLogs.add(createPersonChangeLogEntry(log));
					getPersonService().logChanges(changeLogs);
					stat.incrementMailedFileProcessedCount();

				} catch (Exception ex) {
					stat.incrementMailedFileFailedCount();
					logger.error("Failed to process mailed file entry id = "
							+ log.getCommsLogIdString(), ex);
				}
			}
			// CCR9192 - if log is null
			else {
				// can't find CommsLogEntry
				stat.incrementMailedFileFailedCount();
			}
		}
	}

	private void processCmsErrorFile(CommsImportStatistics stat)
			throws Exception {
		ArrayList cmsErrorFileList = fileDAO.readCmsErrorFile(stat);
		if (cmsErrorFileList == null || cmsErrorFileList.isEmpty())
			return; // changeLogs;

		Set changeLogs = new HashSet();
		CommsLogEntry log = null;
		CmsErrorFileEntry entry = null;

		// NOTE - one letter entry may have multiple error codes (no assumption
		// on order)
		for (int i = 0; i < cmsErrorFileList.size(); i++) {
			try {
				entry = (CmsErrorFileEntry) cmsErrorFileList.get(i);
				EntityKey key = EntityKeyFactory.createEntityKey(
						new BigDecimal(entry.getCommunicationId()),
						CommsLogEntry.class);
				log = (CommsLogEntry) commsLogDAO.getByKey(key);

				if (log != null) {
					// update log in memory
					log.setSentToIVM(Boolean.TRUE);
					log.addMailingStatus(ERROR_BY_CMS_STATUS);
					log.addAacErrorReason(lookupService
							.getComAACErrorTypeByCode(entry.getErrorCode()));
					stat.updateCmsErrorStatistics(entry, true);

					// CCR9192 new - update log for each log entry
					changeLogs.clear();
					changeLogs.add(createPersonChangeLogEntry(log));
					getPersonService().logChanges(changeLogs);
					stat.incrementErrorFileProcessedCount();

					commsLogDAO.update(log);

				} else {
					// can't find CommsLogEntry
					stat.incrementErrorFileFailedCount();
				}
			} catch (Exception ex) {
				stat.incrementErrorFileFailedCount();
				logger
						.error("Failed to log cms error file entry id = "
								+ entry != null ? entry.getCommunicationId()
								: null, ex);
			}
		}

	}

	private void processCmsRejectFile(CommsImportStatistics stat)
			throws Exception {
		ArrayList cmsRejectFileList = fileDAO.readCmsRejectFile(stat);
		if (cmsRejectFileList == null || cmsRejectFileList.isEmpty())
			return;

		Set changeLogs = new HashSet();
		CommsLogEntry log = null;
		CmsRejectFileEntry entry = null;

		for (int i = 0; i < cmsRejectFileList.size(); i++) {
			entry = (CmsRejectFileEntry) cmsRejectFileList.get(i);
			EntityKey key = EntityKeyFactory.createEntityKey(new BigDecimal(
					entry.getCommunicationId()), CommsLogEntry.class);
			log = (CommsLogEntry) commsLogDAO.getByKey(key);

			if (log != null) {
				try {
					// update log
					log.setSentToIVM(Boolean.TRUE);
					log.addMailingStatus(REJECT_BY_CMS_STATUS);
					log.setCode1RejectReason(lookupService
							.getComAACRejectReasonTypeByCode(entry
									.getCode1Reject()));
					commsLogDAO.update(log);

					// CCR 12932 - don't update address to undeliverable (no need for Z05/bulletin)
					//commsLogService.handleMailingResponses(log);

					// update statistics
					stat.updateCmsRejectStatistics(entry);

					// CCR9192 new - update log for each log entry
					changeLogs.clear();
					changeLogs.add(createPersonChangeLogEntry(log));
					getPersonService().logChanges(changeLogs);
					stat.incrementRejectFileProcessedCount();

				} catch (Exception ex) {
					stat.incrementRejectFileFailedCount();
					logger.error("Failed to update comms log status id = "
							+ log.getCommsLogIdString(), ex);
				}
			}
			// CCR9192 - if log is null
			else {
				// can't find CommsLogEntry
				stat.incrementRejectFileFailedCount();
			}
		}
	}

	private void processCmsReturnedMailFile(CommsImportStatistics stat)
			throws Exception {
		ArrayList cmsReturnedFileList = fileDAO.readCmsReturnedMailFile(stat);
		if (cmsReturnedFileList == null || cmsReturnedFileList.isEmpty())
			return;

		Set changeLogs = new HashSet();
		CommsLogEntry log = null;
		CmsReturnedByUSPSFileEntry entry = null;

		for (int i = 0; i < cmsReturnedFileList.size(); i++) {
			entry = (CmsReturnedByUSPSFileEntry) cmsReturnedFileList.get(i);
			EntityKey key = EntityKeyFactory.createEntityKey(new BigDecimal(
					entry.getCommunicationId()), CommsLogEntry.class);
			log = (CommsLogEntry) commsLogDAO.getByKey(key);

			if (log != null) {
				try {
					// update log
					log.setSentToIVM(Boolean.TRUE);
					log.addMailingStatus(RETURNED_BY_POST_OFFICE_STATUS);
					commsLogDAO.update(log);

					// CCR 12932 - don't update address to undeliverable (no need for Z05/bulletin)					
					//commsLogService.handleMailingResponses(log);

					// update statistics
					stat.incrementReturnedMailFileProcessedCount();

					// CCR9192 new - update log for each log entry
					changeLogs.clear();
					changeLogs.add(createPersonChangeLogEntry(log));
					getPersonService().logChanges(changeLogs);

				} catch (Exception ex) {
					stat.incrementRejectFileFailedCount();
					logger.error("Failed to update comms log status id = "
							+ log.getCommsLogIdString(), ex);
				}
			}
			// CCR9192 - if log is null
			else {
				// can't find CommsLogEntry
				// ?????????????????????????????
				stat.incrementRejectFileFailedCount();
			}
		}
	}

	// TODO: make sure the trans log has everything needed for Reporting
	// requirement
	private void updateCommsTransLog(CommsImportStatistics stat)
			throws DAOException {
		if (stat == null)
			return;

		// transmission log for error file
		CommsTransLog errLog = new CommsTransLog();
		errLog.setTransmissionType(IMPORT_TRANSMISSION_ERROR_TYPE);
		errLog.setFileName(stat.getErrorFileName());
		errLog.setFileProcessStartDate(stat.getErrorFileProcessStartTime());
		errLog.setFileProcessEndDate(stat.getErrorFileProcessEndTime());
		errLog.setFileReceivedDate(stat.getErrorFileReceivedDate());
		errLog.setFileRecordCount(new Integer(stat.getErrorFileRecordCount()));

		updateErrorByFormNumber(errLog, stat);

		// transDAO.save(errLog);

		// trnasmission log for reject file
		CommsTransLog rejLog = new CommsTransLog();
		rejLog.setTransmissionType(IMPORT_TRANSMISSION_REJECT_TYPE);
		rejLog.setFileName(stat.getRejectFileName());
		rejLog.setFileProcessStartDate(stat.getRejectFileProcessStartTime());
		rejLog.setFileProcessEndDate(stat.getRejectFileProcessEndTime());
		rejLog.setFileReceivedDate(stat.getRejectFileReceivedDate());
		rejLog.setFileRecordCount(new Integer(stat.getRejectFileRecordCount()));

		updateRejectByFormNumber(rejLog, stat);

		// transDAO.save(rejLog);

		// transmission log for mailed file
		CommsTransLog mailedLog = new CommsTransLog();
		mailedLog.setTransmissionType(IMPORT_TRANSMISSION_MAILED_TYPE);
		mailedLog.setFileName(stat.getMailedFileName());
		mailedLog.setFileProcessStartDate(stat.getMailedFileProcessStartTime());
		mailedLog.setFileProcessEndDate(stat.getMailedFileProcessEndTime());
		mailedLog.setFileReceivedDate(stat.getMailedFileReceivedDate());
		mailedLog.setFileRecordCount(new Integer(stat
				.getMailedFileRecordCount()));

		updateMailedByFormNumber(mailedLog, stat);

		// transDAO.save(mailedLog);

	}

	private void updateErrorByFormNumber(CommsTransLog errLog,
			CommsImportStatistics stat) {
		if (errLog == null || stat == null
				|| stat.getErrorPerTypeNumberTable() == null)
			return;

		String formNumber = null;
		Hashtable errorReasonPerLetterTypeTable = null;
		CommsTransByFormNumber transByForm = null;

		Hashtable errorPerTypeNumberTable = stat.getErrorPerTypeNumberTable();
		Enumeration enumer = errorPerTypeNumberTable.keys();

		while (enumer.hasMoreElements()) {
			transByForm = new CommsTransByFormNumber();
			formNumber = (String) enumer.nextElement();

			transByForm.setFormNumber(formNumber);
			transByForm
					.setFormNumberRecordCount((Integer) errorPerTypeNumberTable
							.get(formNumber));

			errorReasonPerLetterTypeTable = stat
					.getErrorReasonPerLetterTypeTable();
			if (errorReasonPerLetterTypeTable != null)
				transByForm
						.setFormNumberErrorReasons(getFormNumberErrorReasons((Set) errorReasonPerLetterTypeTable
								.get(formNumber)));

			errLog.addTransByFormNumber(transByForm);
		}
	}

	private String getFormNumberErrorReasons(Set errorReasons) {
		if (errorReasons == null || errorReasons.isEmpty())
			return null;
		Iterator it = errorReasons.iterator();
		StringBuffer str = new StringBuffer();
		while (it.hasNext())
			str.append(it.next()).append(ERROR_REASONS_SEPERATOR);

		return str.substring(0, str.length() - 1); // remove the ending
													// ERROR_REASONS_SEPERATOR
	}

	private void updateRejectByFormNumber(CommsTransLog rejLog,
			CommsImportStatistics stat) {
		if (rejLog == null || stat == null)
			return;

		String formNumber = null;
		Hashtable rejectReasonPerLetterTypeTable = null;
		CommsTransByFormNumber transByForm = null;

		Hashtable rejectPerTypeNumberTable = stat.getRejectPerTypeNumberTable();
		Enumeration enumer = rejectPerTypeNumberTable.keys();

		while (enumer.hasMoreElements()) {
			transByForm = new CommsTransByFormNumber();
			formNumber = (String) enumer.nextElement();

			transByForm.setFormNumber(formNumber);
			transByForm
					.setFormNumberRecordCount((Integer) rejectPerTypeNumberTable
							.get(formNumber));

			rejectReasonPerLetterTypeTable = stat
					.getRejectReasonPerLetterTypeTable();
			if (rejectReasonPerLetterTypeTable != null)
				transByForm
						.setFormNumberRejectReasons(getFormNumberRejectReasons((Set) rejectReasonPerLetterTypeTable
								.get(formNumber)));

			rejLog.addTransByFormNumber(transByForm);
		}
	}

	private String getFormNumberRejectReasons(Set rejectReasons) {
		if (rejectReasons == null || rejectReasons.isEmpty())
			return null;

		Iterator it = rejectReasons.iterator();
		StringBuffer str = new StringBuffer();
		while (it.hasNext())
			str.append(it.next()).append(ERROR_REASONS_SEPERATOR);

		return str.substring(0, str.length() - 1); // remove the ending
													// ERROR_REASONS_SEPERATOR
	}

	private void updateMailedByFormNumber(CommsTransLog mailedLog,
			CommsImportStatistics stat) {
		if (mailedLog == null || stat == null)
			return;

		String formNumber = null;
		CommsTransByFormNumber transByForm = null;

		Hashtable mailedPerTypeNumberTable = stat.getMailedPerTypeNumberTable();
		Enumeration enumer = mailedPerTypeNumberTable.keys();

		while (enumer.hasMoreElements()) {
			transByForm = new CommsTransByFormNumber();
			formNumber = (String) enumer.nextElement();

			transByForm.setFormNumber(formNumber);
			transByForm
					.setFormNumberRecordCount((Integer) mailedPerTypeNumberTable
							.get(formNumber));

			mailedLog.addTransByFormNumber(transByForm);
		}
	}

	/**
	 * @return Returns the commsLogService.
	 */
	public CommsLogService getCommsLogService() {
		return commsLogService;
	}

	/**
	 * @param commsLogService
	 *            The commsLogService to set.
	 */
	public void setCommsLogService(CommsLogService commsLogService) {
		this.commsLogService = commsLogService;
	}
}
