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

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import gov.va.med.esr.common.builder.comms.DocumentBuilder;
import gov.va.med.esr.common.builder.comms.EnrollTransmission;
import gov.va.med.esr.common.builder.comms.GeneralTransmission;
import gov.va.med.esr.common.builder.comms.GenerateValues;
import gov.va.med.esr.common.builder.comms.ITransmission;
import gov.va.med.esr.common.builder.comms.InsufficientDataException;
import gov.va.med.esr.common.builder.comms.SsnTransmission;
import gov.va.med.esr.common.model.comms.AacLetterRequest;
import gov.va.med.esr.common.model.comms.CommsLogEntry;
import gov.va.med.esr.common.model.comms.CommsTemplate;
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.financials.IncomeTest;
import gov.va.med.esr.common.model.lookup.ComLetterTemplateType;
import gov.va.med.esr.common.model.lookup.ComLetterType;
import gov.va.med.esr.common.model.lookup.ComMailingTriggerType;
import gov.va.med.esr.common.model.lookup.MessageType;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKeyImpl;
import gov.va.med.esr.common.persistent.comms.AacLetterRequestDAO;
import gov.va.med.esr.common.persistent.comms.CommsLogEntryDAO;
import gov.va.med.esr.common.persistent.comms.CommsTemplateDAO;
import gov.va.med.esr.common.persistent.comms.CommsTransLogDAO;
import gov.va.med.esr.common.persistent.comms.ExportFileDAO;
import gov.va.med.esr.common.util.CommsLetterConstants;
import gov.va.med.esr.service.CommsEmailBulletinService;
import gov.va.med.esr.service.CommsLetterRequestService;
import gov.va.med.esr.service.EligibilityEnrollmentService;
import gov.va.med.esr.service.FinancialsHelperService;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.PersonHelperService;
import gov.va.med.esr.service.impl.AbstractRuleAwareServiceImpl;
import gov.va.med.esr.service.trigger.BulletinTrigger;
import gov.va.med.esr.service.trigger.LetterTriggerEvent;
import gov.va.med.fw.batchprocess.DataProcessExecutionContext;
import gov.va.med.fw.io.writer.FormattedFileWriter;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.service.ServiceException;

public class CmsExportCommandImpl extends AbstractRuleAwareServiceImpl
		implements CmsExportCommand {

	/**
	 * 
	 */
	private static final long serialVersionUID = 2230816708946289387L;
	
	private static int DEFAULT_BATCH_SIZE = 1000;
	private final String BATCH_SIZE_ARG = "-size=";
    final private static String ZERO_SIZE_FILE_NAME = "     N/A     "; //with some padding	
    private static String EXPORT_TRANS_TYPE_CODE = "COMREQ-A";
    private int batchSize = DEFAULT_BATCH_SIZE;
    
    private static MessageType EXPORT_TRANSMISSION_TYPE = null;
	private ExportFileDAO fileDAO = null;
    private AacLetterRequestDAO requestDAO = null;
    private PersonHelperService personHelperService;
    private CommsLogEntryDAO commsLogDAO = null;
    private CommsTemplateDAO templateDAO;
    private CommsLetterRequestService letterRequestService = null;    
    private EligibilityEnrollmentService eligibilityEnrollmentService;    
    private CommsTransLogDAO transDAO = null;
    private FormattedFileWriter fileWriter;    

	public CmsExportCommandImpl() {
		super();
	}

	public CommsExportStatistics execute(String[] args,
			DataProcessExecutionContext context,
			InitiateCMSLetterExportProcess process) throws Exception {
        String option = null;

        
        if (args == null || args.length == 0) {
			option = "-P"; // default
		} else {
			for (int i = 0; i < args.length; i++) {
				String currArg = args[i];
				if (currArg.startsWith(BATCH_SIZE_ARG)) {
					try {
						String reqPerBatchSize = currArg
								.substring(BATCH_SIZE_ARG.length());
						batchSize = new Integer(reqPerBatchSize).intValue();
					} catch (Exception e) {
						// if there is an exception igonre it we will continue
						// with default batchSize
						logger.error("Exception to Parse the Args :" + e);
					}
				} else {
					option = args[0].trim().toUpperCase();
				}
			}
		}

		if (option == null || args == null || args.length == 0) {
			option = "-P"; // default
		}
           
        //start the real process
        CommsExportStatistics stats = new CommsExportStatistics();
        stats.setStartTime(new Date());

        StringBuffer totalCmsLetterText = new StringBuffer();
        String aacExtractId = fileDAO.getAacExtractId();

        stats.setFileName(fileDAO.getAacFileName(aacExtractId));
        stats.setOriginalFileName(fileDAO.getOriginalAacFileName(aacExtractId));

    	LookupService lookupService = (LookupService) getComponent("lookupService");
		EXPORT_TRANSMISSION_TYPE = lookupService.getMessageTypeByCode(EXPORT_TRANS_TYPE_CODE);        
        
        List requests = requestDAO.findCmsLetterRequests();

        executeBatch(context,process, requests, stats, totalCmsLetterText,aacExtractId);

        if ( stats.getNumberSent() <=  0 )
        {
            //since there is no file, set the file name to be zero size file name
            stats.setFileName(ZERO_SIZE_FILE_NAME);
        }
        
        //Now change the .tmp file to .ltr file
        try {
            fileDAO.changeCmsTmpFilesToLtr();            
        } catch (IOException e) {
            logger.error("Exception occured while changing the files from .tmp to .ltr ", e);
        }
        
        stats.setEndTime(new Date());

        updateCommsTransLog(stats);

        //Send email to email bulletin
        sendEmailNotification(stats);

        return stats;
	}

	public void executeSingle(DataProcessExecutionContext context,
			InitiateCMSLetterExportProcess process, AacLetterRequest request,
			CommsExportStatistics stats, String aacExtractId) throws Exception {

		// letters sent count per request.
		int entryCount = 0;
		StringBuffer cmsLetterText = new StringBuffer();

		CommsLogEntry log = request.getCommsLogEntry();
		Person person = getPersonService().getPerson(
				new PersonIdEntityKeyImpl(request.getCommsLogEntry()
						.getPersonId()));

		GenerateValues inputValues = this.getInputValues(person, request);
		inputValues.setAacExtractId(aacExtractId);

		// set the mailing address in the log entry so that we can
		// check the letter rules
		log.setAddress(((GeneralTransmission) inputValues.getTransmission())
				.getMailingAdress());

		// Make sure the letter passes the rules by ILog Rule Engine
		// and update the log status and error reasons (if any)
		// according to the rule results
		List errorMessageList = this.getLetterRequestService()
				.processLetterRulesForAACExport(
						person,
						getFormType(log.getFormNumber()),
						log.getWorkflowCaseId() == null ? null : log
								.getWorkflowCaseId().toString(),
						getTriggerType(log.getComMailingTriggerType()),
						log.getOverrideIndicator(), log,
						inputValues.getCategoryType());
		if (containsErrors(errorMessageList)) {
			if (logger.isDebugEnabled()) {
				logger.debug("has errors update log ");
			}

			// update the statistics for reject at HEC
			stats.updateRejectPerReasonPerformTable(log);

			// increment the reject count
			stats.setNumberRejected(stats.getNumberRejected() + 1);
		} else {
			// build the actual ASCII text for CMS file
			List cmsLetterTextEntryLst = buildLetterRequests(person,
					inputValues);

			// update the comms log accordingly
			String vetFileData = (String) cmsLetterTextEntryLst.get(0);
			updateVeteranCommsLog(log, inputValues, vetFileData);
			cmsLetterText.append(vetFileData);
			entryCount++;

			// update the statistics
			if (CommsLogEntry.REMAIL_INDICATOR_RESEND.equalsIgnoreCase(log
					.getRemailIndicator()))
				stats.setNumberRemailed(stats.getNumberRemailed() + 1);
			stats.addToCountPerFormTable(request);
		}
		// reset this form for this Person
		request.setCommsPrintRequestDupeCheck(request.getEntityKey()
				.getKeyValueAsString());
		requestDAO.mergeObject(request);
		if (cmsLetterText.length() > 0) {
			String form = request.getCommsTemplateFormNumber();
			fileDAO.saveCms(cmsLetterText.toString(), aacExtractId);
		}

		stats.setNumberSent(stats.getNumberSent() + entryCount);

	}

	public void executeBatch(DataProcessExecutionContext context,
			InitiateCMSLetterExportProcess process, List requests,
			CommsExportStatistics stats, StringBuffer totalCmsLetterText,
			String aacExtractId) throws Exception {

		AacLetterRequest request = null;

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

			// letters sent count per request.
			int entryCount = 0;
			try {
				if (logger.isDebugEnabled()) {
					logger.debug("processing requet: " + i);
				}

				request = (AacLetterRequest) requests.get(i);
				boolean isValidRequest = isValidRequest(request, stats);

				if (!isValidRequest) {
					// log and continue to the next request we
					// all ready added the stats in isValidRequest()

					if (logger.isDebugEnabled()) {
						logger.debug("requet is not valid: " + i);
					}

					continue;
				}

				CmsExportCommand cmd = (CmsExportCommand) getComponent("cmsExportCommand");
				// do this in new transaction
				cmd.executeSingle(context, process, request, stats,
						aacExtractId);

			} catch (Exception ex) {
				stats
						.setNumberWithException(stats.getNumberWithException() + 1);

				// Failed to write the file. This is not recoverable.
				if (ex.getCause() != null
						&& ex.getCause() instanceof java.io.IOException) {
					context.setInterrupted(true);
				}

				handleException(context, request, ex);
			} finally {
				// Save after each request is processed
				stats.setNumberSent(stats.getNumberSent() + entryCount);

				if (process.isInterrupted(context)) {
					break;
				}

				process.updateLiveJobStatistics(context, stats);
			}
		}
	}

	private boolean isValidRequest(AacLetterRequest request,
			CommsExportStatistics stats) {
		CommsLogEntry entry = (CommsLogEntry) request.getCommsLogEntry();
		boolean isValid = false;

		// only look for request that's Send to CMS (NOT old handbooks)
		boolean isIVM = ComLetterType.CODE_IVM.getCode().equals(
				entry.getLetterType().getCode());
		if (entry.isSendToCMS() && isIVM)
			isValid = true;

		return isValid;
	}

	private void handleException(DataProcessExecutionContext executionContext,
			AacLetterRequest request, Exception e) {
		String errorMessage = "Error processing record in CMS Letter Export: "
				+ " Reason: " + e.getMessage();

		if (request != null && request.getCommsLogEntry() != null) {
			errorMessage = errorMessage + " Person ID: "
					+ request.getCommsLogEntry().getPersonId()
					+ " Form Number: "
					+ request.getCommsLogEntry().getFormNumber();
		}

		executionContext.getExceptionData().add(errorMessage);

		if (logger.isErrorEnabled())
			logger.error(errorMessage, e);
		// Writes the exception data to a .exception file.
		ArrayList exceptionData = new ArrayList();
		exceptionData.add(errorMessage);
		if (e != null) {
			StringWriter sw = new StringWriter();
			e.printStackTrace(new PrintWriter(sw));
			String trackTrace = sw.toString();
			exceptionData.add(trackTrace);
		}
		try {
			getFileWriter().appendData(exceptionData);
		} catch (Exception e1) {
			logger.error("Unable to write the exception to .exception file ",
					e1);
		}
	}

	private boolean isIVMFinalLetter(String form) {
		if (form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_400
				.getCode())
				|| form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_450
						.getCode())
				|| form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_451
						.getCode())
				|| form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_452
						.getCode())
				|| form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_453
						.getCode())
				|| form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_454
						.getCode())
				|| form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_455
						.getCode())) {
			return true;
		}
		return false;
	}

	private boolean isCompassionLetter(String form) {
		if (form.equalsIgnoreCase(ComLetterTemplateType.FORM_NUMBER_683A
				.getCode())) {
			return true;
		}
		return false;
	}

	private List buildLetterRequests(Person person, GenerateValues inputValues)
			throws ServiceException {
		DocumentBuilder builder = new DocumentBuilder();

		// build the ASCII text
		List textLst = null;
		try {
			// builder return a list of ASCII text String
			// for now, index 0 as veteran letter, index 1+ as POA letter if
			// exists
			textLst = builder.build(person, inputValues);
		} catch (InsufficientDataException idex) {
			logger
					.error(
							"ERROR: Insufficient Data to build a letter while requesting a CMS letter",
							idex);
			throw new ServiceException(idex.toString());
		}

		return textLst;
	}

	private void updateVeteranCommsLog(CommsLogEntry log,
			GenerateValues inputValues, String vetFileData) throws DAOException {
		// Veteran log, update the logXml
		log.setLogXML(vetFileData);
		log.setAacExtractNumber(inputValues.getAacExtractId());
	}

	private boolean containsErrors(List errors) {
		boolean result = false;
		if (errors != null) {
			result = errors.size() >= 1 ? !((List) errors.get(0)).isEmpty()
					: false;
			if (!result)
				result = errors.size() >= 2 ? !((List) errors.get(1)).isEmpty()
						: false;
		}
		return result;
	}

	private LetterTriggerEvent.MailType getTriggerType(
			ComMailingTriggerType triggerType) {
		if (triggerType != null) {
			String triggerTypeCode = triggerType.getCode();

			if (triggerTypeCode.equals(CommsLogEntry.MAMUAL_TRIGGER_CODE))
				return LetterTriggerEvent.MANUAL_MAIL;
			if (triggerTypeCode.equals(CommsLogEntry.REMAIL_TRIGGER_CODE))
				return LetterTriggerEvent.REMAIL_MAIL;
			if (triggerTypeCode.equals(CommsLogEntry.HISTORICAL_TRIGGER_CODE))
				return LetterTriggerEvent.HISTORIC_MAIL;
			if (triggerTypeCode.equals(CommsLogEntry.AUTOMATIC_TRIGGER_CODE))
				return LetterTriggerEvent.AUTO_MAIL;
		}
		return null;
	}

	private ComLetterTemplateType getFormType(String formNumber)
			throws Exception {
		if (formNumber == null)
			return null;

		return this.getLookupService().getComLetterTemplateTypeByCode(
				formNumber);
	}

	private GenerateValues getInputValues(Person person,
			AacLetterRequest request) throws ServiceException {
		CommsLogEntry log = request.getCommsLogEntry();
		CommsTemplate templ = log.getTemplate();
		String formNumber = templ.getCommsTemplateFormNumber();

		ITransmission trans = getTransmission(formNumber, person, request
				.getCommsLogEntry());

		GenerateValues inputValues = new GenerateValues(person, formNumber,
				null, log.getWorkflowCaseId(), trans);
		inputValues.setPrintLocationCd(CommsLetterConstants.MAIL_CENTER_CMS);

		inputValues.setOrigTemplate(templ);
		inputValues.setCommsLog(log);
		if (request.isVeteranLetter())
			inputValues.setCategoryType(AacLetterRequest.VETERAN_LETTER);
		else if (request.isSpouseLetter())
			inputValues.setCategoryType(AacLetterRequest.SPOUSE_LETTER);
		else if (request.isDependentLetter())
			inputValues.setCategoryType(AacLetterRequest.DEPENDENT_LETTER);

		return inputValues;
	}

	private ITransmission getTransmission(String formNumber, Person person,
			CommsLogEntry log) throws ServiceException {
		if (formNumber == null)
			return null;

		ITransmission trans = null;

		if (ComLetterTemplateType.FORM_NUMBER_290.getCode().equals(formNumber)
				|| ComLetterTemplateType.FORM_NUMBER_291.getCode().equals(
						formNumber)
				|| ComLetterTemplateType.FORM_NUMBER_292.getCode().equals(
						formNumber)
				|| ComLetterTemplateType.FORM_NUMBER_293.getCode().equals(
						formNumber)
				|| ComLetterTemplateType.FORM_NUMBER_298.getCode().equals(
						formNumber)
				|| ComLetterTemplateType.FORM_NUMBER_299.getCode().equals(
						formNumber)) {
			SsnTransmission ssnTrans = new SsnTransmission(person, log
					.getWorkflowCaseId());
			ssnTrans.setFormNumber(formNumber);
			ssnTrans.setHelperService(this.getHelperService());
			trans = ssnTrans;
		} else {
			// get the data objects - transmission and inputValues
			EnrollTransmission enrTrans = new EnrollTransmission(person, log
					.getWorkflowCaseId());
			enrTrans.setFormNumber(formNumber);
			enrTrans.setHelperService(this.getHelperService());

			// must set "prior" enrollment determination on EnrollTransmission
			// TODO: optimize this api, no need to get the entire Person!
			/**
			 * Commented unused method CR 8110 Person priorPerson =
			 * this.eligibilityEnrollmentService
			 * .getPersonForPriorEnrollment(person.getEntityKey());
			 * if(priorPerson != null) {
			 * enrTrans.setPriorEnrollmentDetermination
			 * (priorPerson.getEnrollmentDetermination()); }
			 ***/

			String mostRecentNonNullPriorityLevel = this.eligibilityEnrollmentService
					.getMostRecentNonNullPriorityLevelByDate(person
							.getEntityKey(), log.getCreatedOn());
			enrTrans
					.setMostRecentNonNullPriorityLevel(mostRecentNonNullPriorityLevel);

			// init "current" IncomeTest
			IncomeTest currentIncomeTest = personHelperService
					.getCurrentIncomeTest(person);
			enrTrans.setLocalIncomeTest(currentIncomeTest);

			// calculate and set income threshold for financial information
			if (currentIncomeTest != null) {
				Integer currentIncomeYear = currentIncomeTest.getIncomeYear();

				if (currentIncomeYear != null) {
					Integer total = (currentIncomeTest
							.getTotalNumberOfDependents() != null) ? currentIncomeTest
							.getTotalNumberOfDependents()
							: new Integer(0);
					enrTrans
							.setIncomeThreshold(((FinancialsHelperService) this
									.getComponent("financialsHelperService"))
									.calculateIncomeThresholds(
											currentIncomeYear, total));
				}
			}
			trans = enrTrans;

		}

		return trans;
	}

	private void sendEmailNotification(CommsExportStatistics stats)
			throws ServiceException {

		Hashtable dataTab = new Hashtable();
		dataTab.put("CompletedDateTime", stats.getEndTime().toString());
		dataTab.put("NumberOfFiles", "1");
		dataTab.put("NumberLettersSent", stats.getNumberSent() + "");
		dataTab.put("NumberLetterRejected", stats.getNumberRejected() + "");
		dataTab.put("NumberLettersRemailed", stats.getNumberRemailed() + "");
		dataTab.put("CommunicationsPerFormTxt", stats
				.getCountPerFormTableText());
		dataTab.put("RejectPerReasonPerFormTxt", stats
				.getRejectPerReasonPerFormText());
		dataTab.put("FileNameRecordCountTxt", stats.getOriginalFileName()
				+ "          " + stats.getNumberSent());

		CommsEmailBulletinService emailSrv = null;

		emailSrv = (CommsEmailBulletinService) getComponent("commsEmailBulletinService");
		emailSrv.sendEmailBulletin(
				BulletinTrigger.DataType.ENROLLMENT_LETTER_PROCESSING, dataTab,
				null);

	}

	private void updateCommsTransLog(CommsExportStatistics stats)
			throws Exception {

		if (stats.getFileName() == null)
			return;

		Hashtable countPerformTab = stats.getCountPerFormTable();
		if (countPerformTab != null && countPerformTab.size() > 0) {

			Enumeration keys = countPerformTab.keys();
			String key = null;

			CommsTransLog entry = null;
			while (keys.hasMoreElements()) {
				key = (String) keys.nextElement();
				entry = new CommsTransLog();

				// entry.setFileName(stats.getFileName()); Commented this, as
				// this will get .tmp file
				entry.setFileName(stats.getOriginalFileName());
				entry.setFileProcessStartDate(stats.getStartTime());
				entry.setFileProcessEndDate(stats.getEndTime());

				CommsTransByFormNumber transByFormNumber = new CommsTransByFormNumber();
				transByFormNumber.setFormNumber(key);
				transByFormNumber
						.setFormNumberRecordCount((Integer) countPerformTab
								.get(key));
				entry.addTransByFormNumber(transByFormNumber);

				entry.setTransmissionType(this.getLookupService()
						.getMessageTypeByCode(EXPORT_TRANS_TYPE_CODE));
				transDAO.save(entry);
			}
		}
	}	   

	public int getBatchSize() {
		return this.batchSize;
	}

	public CommsLogEntryDAO getCommsLogDAO() {
		return this.commsLogDAO;
	}

	public EligibilityEnrollmentService getEligibilityEnrollmentService() {
		return this.eligibilityEnrollmentService;
	}

	public ExportFileDAO getFileDAO() {
		return this.fileDAO;
	}

	public FormattedFileWriter getFileWriter() {
		return this.fileWriter;
	}

	public PersonHelperService getPersonHelperService() {
		return this.personHelperService;
	}


	public AacLetterRequestDAO getRequestDAO() {
		return this.requestDAO;
	}

	public CommsTemplateDAO getTemplateDAO() {
		return this.templateDAO;
	}

	public CommsTransLogDAO getTransDAO() {
		return this.transDAO;
	}

	public void setBatchSize(int batchSize) {
		this.batchSize = batchSize;
	}

	public void setCommsLogDAO(CommsLogEntryDAO commsLogDAO) {
		this.commsLogDAO = commsLogDAO;
	}

	public void setEligibilityEnrollmentService(
			EligibilityEnrollmentService eligibilityEnrollmentService) {
		this.eligibilityEnrollmentService = eligibilityEnrollmentService;
	}


	public void setFileDAO(ExportFileDAO fileDAO) {
		this.fileDAO = fileDAO;
	}


	public void setFileWriter(FormattedFileWriter fileWriter) {
		this.fileWriter = fileWriter;
	}


	public void setPersonHelperService(PersonHelperService personHelperService) {
		this.personHelperService = personHelperService;
	}


	public void setRequestDAO(AacLetterRequestDAO requestDAO) {
		this.requestDAO = requestDAO;
	}

	public void setTemplateDAO(CommsTemplateDAO templateDAO) {
		this.templateDAO = templateDAO;
	}

	public void setTransDAO(CommsTransLogDAO transDAO) {
		this.transDAO = transDAO;
	}

	public CommsLetterRequestService getLetterRequestService() {
		return letterRequestService;
	}

	public void setLetterRequestService(CommsLetterRequestService letterReqService) {
		this.letterRequestService = letterReqService;
	}
}
