package gov.va.med.esr.voa.validator;

import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.lookup.VAFacilityType;
import gov.va.med.esr.voa.common.ErrorCode;
import gov.va.med.esr.voa.webservice.AddressCollection;
import gov.va.med.esr.voa.webservice.AddressInfo;
import gov.va.med.esr.voa.webservice.ApplicationCollection;
import gov.va.med.esr.voa.webservice.ApplicationInfo;
import gov.va.med.esr.voa.webservice.ContactInfo;
import gov.va.med.esr.voa.webservice.DemographicInfo;
import gov.va.med.esr.voa.webservice.EeSummary;
import gov.va.med.esr.voa.webservice.EnrollmentDeterminationInfo;
import gov.va.med.esr.voa.webservice.FinancialsInfo;
import gov.va.med.esr.voa.webservice.Form;
import gov.va.med.esr.voa.webservice.FormIdentifier;
import gov.va.med.esr.voa.webservice.InsuranceCollection;
import gov.va.med.esr.voa.webservice.InsuranceInfo;
import gov.va.med.esr.voa.webservice.MilitaryServiceEpisodeCollection;
import gov.va.med.esr.voa.webservice.MilitaryServiceEpisodeInfo;
import gov.va.med.esr.voa.webservice.MilitaryServiceInfo;
import gov.va.med.esr.voa.webservice.MilitaryServiceSiteRecordCollection;
import gov.va.med.esr.voa.webservice.MilitaryServiceSiteRecordInfo;
import gov.va.med.esr.voa.webservice.PersonInfo;

import java.util.Iterator;
import java.util.List;

import javax.xml.datatype.XMLGregorianCalendar;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;

/**
 * Form validation
 */
public class FormValidator extends AbstractValidator {

	// 1010EZ form type
	private static final String TYPE_1010EZ = "100";

	// CCR 13857 - 21526EZ form type
	private static final String TYPE_21526EZ = "105";

	// personInformation validator
	private static final PersonInfoValidator personInfoValidator = new PersonInfoValidator();

	// demographicInformation validator
	private static final DemographicInfoValidator demographicInfoValidator = new DemographicInfoValidator();

	// enrollment determination information validator
	private static final EnrollmentDeterminationInfoValidator enrollmentDeterminationInfoValidator = new EnrollmentDeterminationInfoValidator();


	@SuppressWarnings("rawtypes")
	@Override
	public boolean supports(Class clazz) {
		return Form.class.equals(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		 if (logger.isDebugEnabled()){
			 logger.debug("validate form.");
		 }

		if (target != null) {
			Form form = (Form) target;
			EeSummary eeSummary = form.getSummary();
			if (eeSummary != null) {
				PersonInfo personInfo = eeSummary.getPersonInfo();
				if (personInfo != null) {
					ValidationUtils.invokeValidator(personInfoValidator,
							personInfo, errors);
				}

				DemographicInfo demographicInfo = eeSummary.getDemographics();

				//CCR 13857 - auto-enrollment from VBA, auto-set PF to nearest in form data
				if (form.getFormIdentifier().getType().equalsIgnoreCase(TYPE_21526EZ)) {

					AddressInfo addressInfo = null;
					String zipCode = null;
					ContactInfo contactInfo = demographicInfo.getContactInfo();

					if (contactInfo != null) {
						AddressCollection addressCollection = contactInfo
								.getAddresses();
						if (addressCollection != null) {
							List<AddressInfo> addressInfoList = addressCollection.getAddress();
							if (!addressInfoList.isEmpty())
								addressInfo  = addressInfoList.get(0);
							}
					}

					if (addressInfo != null) {
						 zipCode = addressInfo.getZipCode();
						 try {
							 VAFacility nearest = findNearestFacilityByZip(zipCode);
							 if (nearest != null) {
								 demographicInfo.setPreferredFacility(nearest.getStationNumber());
							 } else {
								 logger.info("VOA 526 PF nearest facility is Null for zip code"); // + zipCode);
								 errors.reject(ErrorCode.VOA_0021, "Nearest VA Facility cannot be determined from zip code.");
							 }
						 } catch (Exception e) {
							 logger.info("VOA 526 PF failed for Exception: " + e.getMessage());
							 errors.reject(ErrorCode.VOA_0021, "Nearest VA Facility cannot be determined from zip code.");
						 }
					} else {
						logger.info("VOA 526 PF failed for Null AddressInfo");
						errors.reject(ErrorCode.VOA_0021, "Nearest VA Facility cannot be determined from zip code.");
					}
				}


				if (demographicInfo != null) {
					ValidationUtils.invokeValidator(demographicInfoValidator,
							demographicInfo, errors);
				}

				EnrollmentDeterminationInfo enrollmentDeterminationInfo = eeSummary
						.getEnrollmentDeterminationInfo();
				if (enrollmentDeterminationInfo != null) {
					ValidationUtils.invokeValidator(
							enrollmentDeterminationInfoValidator,
							enrollmentDeterminationInfo, errors);
				}

				InsuranceCollection insuranceCollection = eeSummary
						.getInsuranceList();
				if (insuranceCollection != null) {
					List<InsuranceInfo> insuranceInfoList = insuranceCollection
							.getInsurance();
					for (InsuranceInfo insuranceInfo : insuranceInfoList) {
						ValidationUtils.invokeValidator(
								new InsuranceInfoValidator(), insuranceInfo,
								errors);
					}
				}

				MilitaryServiceInfo militaryServiceInfo = eeSummary
						.getMilitaryServiceInfo();
				if (militaryServiceInfo != null) {
					MilitaryServiceSiteRecordCollection militaryServiceSiteRecordCollection = militaryServiceInfo
							.getMilitaryServiceSiteRecords();
					if (militaryServiceSiteRecordCollection != null) {
						List<MilitaryServiceSiteRecordInfo> militaryServiceSiteRecordInfoList = militaryServiceSiteRecordCollection
								.getMilitaryServiceSiteRecord();
						for (MilitaryServiceSiteRecordInfo militaryServiceSiteRecordInfo : militaryServiceSiteRecordInfoList) {
							MilitaryServiceEpisodeCollection militaryServiceEpisodeCollection = militaryServiceSiteRecordInfo
									.getMilitaryServiceEpisodes();
							if (militaryServiceEpisodeCollection != null) {
								List<MilitaryServiceEpisodeInfo> militaryServiceEpisodeInfoList = militaryServiceEpisodeCollection
										.getMilitaryServiceEpisode();
								for (MilitaryServiceEpisodeInfo militaryServiceEpisodeInfo : militaryServiceEpisodeInfoList) {
									ValidationUtils
											.invokeValidator(
													new MilitaryServiceEpisodeInfoValidator(),
													militaryServiceEpisodeInfo,
													errors);
								}
							}
						}
					}
				}

				FinancialsInfo financialsInfo = eeSummary.getFinancialsInfo();
				if (financialsInfo != null) {
					//CCR 13311 remove spouse employment

					// validate veteran financial info
					ValidationUtils.invokeValidator(
							new FinancialInfoValidator(), financialsInfo,
							errors);
				}
			}

			validateAppDate(form, errors);

			 if (logger.isDebugEnabled()){
				 logger.debug("validate form - Done:" +  form);
			 }
		}
	}

	//CCR13857 - auto-enrollment from VBA, find closest logical zip code
	@SuppressWarnings("rawtypes")
	private VAFacility findNearestFacilityByZip(String zip) throws Exception {

		if (zip == null) return null;

		List facilities = getLookupService().getAllVaFacilitys();

		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 && vamc.getType().getIsMedicalTreating()) {
					if (vamc.getType().getCode().equals(VAFacilityType.CODE_OPC.getName()) || vamc.getType().getCode().equals(VAFacilityType.CODE_CBOC.getName())
							|| vamc.getType().getCode().equals(VAFacilityType.CODE_VAMC.getName())) {

						if (vamc.getStreetAddress() != null && vamc.getStreetAddress().getZipCode() != null) {
							String vaZip = vamc.getStreetAddress().getZipCode().substring(0, level);
							if (zip.equals(vaZip)) {
								match = true;
								nearest = vamc;
								logger.info("setting VOA form 526 PF for zip code level match:" + vaZip + " to site:" + vamc.getStationNumber());
								break;
							}
						}
					}
				}
			}
			level--;
		}

		return nearest;
	}

	/**
	 * application Date is a required field for 1010EZ form.
	 * also in 21526EZ form per CCR13857
	 * @param form
	 */
	private void validateAppDate(Form form, Errors errors) {
		FormIdentifier formIdentifier = form.getFormIdentifier();
		if (formIdentifier != null) {
			String formType = formIdentifier.getType();
			//validate 1010EZ and
			//also newly created type 21526EZ per CCR13857
			if (TYPE_1010EZ.equalsIgnoreCase(formType) || TYPE_21526EZ.equalsIgnoreCase(formType) ) {
				XMLGregorianCalendar appDate = null;
				ApplicationCollection applicationCollection = form.getApplications();
				if (applicationCollection != null) {
					List<ApplicationInfo> applicationInfoList = applicationCollection.getApplicationInfo();
					for (ApplicationInfo applicationInfo : applicationInfoList) {
						XMLGregorianCalendar temp = applicationInfo.getAppDate();
						if (temp != null) {
							appDate = temp;
							break;
						}
					}
				}

				if (appDate == null) {
				   if (TYPE_1010EZ.equalsIgnoreCase(formType)) {
					errors.reject(ErrorCode.VOA_0248,
							"Missing required field for 10-10EZ Application:  Application Date.");
					 if (logger.isDebugEnabled()){
				   		 logger.debug("Missing required field for 10-10EZ Application:  Application Date.");
				   }
				  }
				  else if (TYPE_21526EZ.equalsIgnoreCase(formType)) {  //added for newly created type 21526EZ per CCR13857
							errors.reject(ErrorCode.VOA_0253,
									"Missing required field for 21526EZ Application:  Application Date.");
					 if (logger.isDebugEnabled()){
						 logger.debug("Missing required field for 21526EZ Application:  Application Date.");
					 }
				 }
				}
			}
		}
	}

}
