package gov.va.vss.service.impl;

import static gov.va.vss.model.notification.NotificationLinkType.VOLUNTEER_PROFILE;
import static gov.va.vss.model.requirement.RequirementStatus.RequirementStatusValue.MET;
import static java.time.temporal.ChronoUnit.DAYS;

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import gov.va.shared.model.Permission.PermissionType;
import gov.va.shared.model.Role;
import gov.va.shared.model.Role.RoleType;
import gov.va.shared.service.validation.ServiceValidationException;
import gov.va.shared.util.SecurityUtil;
import gov.va.vss.model.facility.Facility;
import gov.va.vss.model.notification.Notification;
import gov.va.vss.model.notification.NotificationSeverityType;
import gov.va.vss.model.notification.NotificationType;
import gov.va.vss.model.requirement.VolunteerRequirement;
import gov.va.vss.model.voluntaryService.VoluntaryServiceStaff;
import gov.va.vss.service.NotificationDispositionType;
import gov.va.vss.service.NotificationService;
import gov.va.vss.service.email.EmailService;
import gov.va.vss.util.DateUtil;

@Service
public class NotificationServiceImpl extends AbstractServiceImpl implements NotificationService {
	private static final Logger log = LoggerFactory.getLogger(NotificationServiceImpl.class);

	@Autowired
	private EmailService emailService;

	@Override
	public Notification saveOrUpdate(Notification notification) {
		boolean isNew = !notification.isPersistent();
		notification = notificationDAO.saveOrUpdate(notification);

		if (isNew && notification.getSeverity() == NotificationSeverityType.HIGH) {
			List<VoluntaryServiceStaff> allStaff = voluntaryServiceStaffDAO.findLinkedToNotification(notification);
			for (VoluntaryServiceStaff s : allStaff) {
				if (!s.isEmailNotifications())
					continue;

				String email = s.getAppUser().getEmail();
				if (StringUtils.isNotBlank(email)) {
					try {
						emailService.sendEmail(notification.getName(), notification.getDescription(),
								new String[] { email }, null);
					} catch (Exception e) {
						log.error("There was an error sending an email to " + email, e);
					}
				} else {
					log.error(
							"The voluntary service staff member with ID {} registered for notifications but does not have a valid email on file.",
							s.getAppUser().getId());
				}
			}
		}

		return notification;
	}

	@Override
	public void delete(long notificationId) throws ServiceValidationException {
		Notification n = notificationDAO.findRequiredByPrimaryKey(notificationId);
		if (!n.isClearable())
			throw new ServiceValidationException("notification.error.notificationNotClearable");
		notificationDAO.delete(notificationId);
	}

	@Override
	@Transactional(readOnly = true)
	public NotificationSearchResult getRequirementNotificationsForFacility(long facilityId) {
		
		boolean isNationalAdmin = getCurrentUser().isNationalAdmin();
		boolean isFacilityAdmin = SecurityUtil
				.hasAnyPermissionAtCurrentFacility(PermissionType.REQUIREMENTS_LOCAL_MANAGE);
		if (!isNationalAdmin && !isFacilityAdmin)
			return new NotificationSearchResult(new ArrayList<>());

		List<Notification> notifications = new ArrayList<>();

		if (isFacilityAdmin) {
			List<VolunteerRequirement> volReqs = volunteerRequirementDAO.findForExpiringRequirements(facilityId, null,
					-1);
			List<Notification> volNotifications = getNotificationsForExpiringRequirements(facilityId, volReqs);
			notifications.addAll(volNotifications);
		}

		return new NotificationSearchResult(notifications);
	}
	
	@Override
	@Transactional(readOnly = true)
	public NotificationSearchResult getNotificationsForFacility(long facilityId) {
		LocalDate today = getTodayAtFacility();

		boolean isNationalAdmin = getCurrentUser().isNationalAdmin();
		boolean isFacilityAdmin = SecurityUtil
				.hasAnyPermissionAtCurrentFacility(PermissionType.REQUIREMENTS_LOCAL_MANAGE);
		if (!isNationalAdmin && !isFacilityAdmin)
			return new NotificationSearchResult(new ArrayList<>());

		List<Notification> notifications = new ArrayList<>();
		if (isNationalAdmin || isFacilityAdmin) {
			// boolean implied by above but just including for clarity
			notifications.addAll(notificationDAO.findByUserAndFacility(today, getCurrentUser().getId(), facilityId));
		}
		
		Collections.sort(notifications);

		return new NotificationSearchResult(notifications);
	}

	public NotificationDispositionType getNotificationDisposition(LocalDate today, VolunteerRequirement vr) {
		LocalDate vrDate = vr.getRequirementDate();
		boolean dateTypeHasNotification = !vr.getRequirement().getDateType().isSkipNotification();

		if (vrDate != null && dateTypeHasNotification && !today.isBefore(vrDate)) {
			return NotificationDispositionType.EXPIRED;
		} else {
			if (vr.getStatus().getLookupType() != MET) {
				return NotificationDispositionType.UNMET;
			} else if (vrDate != null) {
				Integer daysNotification = vr.getRequirement().getDaysNotification();
				if (dateTypeHasNotification && daysNotification != null
						&& DAYS.between(today, vrDate) <= daysNotification) {
					return NotificationDispositionType.WARNING;
				}
			}
		}

		return null;
	}

	public LocalDate getNotificationBeginDateInZone(VolunteerRequirement vr, ZoneId timeZone) {
		LocalDate today = LocalDate.now(timeZone);
		LocalDate vrDate = vr.getRequirementDate();
		boolean dateTypeHasNotification = !vr.getRequirement().getDateType().isSkipNotification();

		if (vrDate != null && dateTypeHasNotification && !today.isBefore(vrDate)) {
			return vrDate;
		} else {
			if (vr.getStatus().getLookupType() != MET) {
				return vr.getCreatedDate().withZoneSameInstant(timeZone).toLocalDate();
			} else if (vrDate != null) {
				Integer daysNotification = vr.getRequirement().getDaysNotification();
				if (dateTypeHasNotification && daysNotification != null
						&& DAYS.between(today, vrDate) <= daysNotification) {
					return vrDate.minusDays(daysNotification);
				}
			}
		}

		return null;
	}

	public List<Notification> getNotificationsForExpiringRequirements(long facilityId,
			List<VolunteerRequirement> volReqs) {
		LocalDate today = getTodayAtFacility();
		Facility facility = facilityDAO.findRequiredByPrimaryKey(facilityId);
		Role facilityAdminRole = roleDAO.findByLookup(RoleType.SITE_ADMINISTRATOR);
		List<Notification> volNotifications = new ArrayList<>();

		for (VolunteerRequirement vr : volReqs) {
			NotificationDispositionType disposition = getNotificationDisposition(today, vr);
			LocalDate beginDate = getNotificationBeginDateInZone(vr, facility.getTimeZone());
			if (disposition == null || beginDate == null)
				continue;

			String requirementName = vr.getRequirement().getName();
			long volunteerRequirementId = vr.getId();
			LocalDate vrDate = vr.getRequirementDate();
			String volRequirementStatus = vr.getStatus().getName();

			LocalDate expirationDate = vrDate;
			Integer daysUntilExpiration = expirationDate != null ? (int) DAYS.between(today, vrDate) : null;

			String name = null;
			String description = null;
			NotificationSeverityType severityType = NotificationSeverityType.LOW;

			if (disposition == NotificationDispositionType.UNMET) {
				name = "Requirement Unmet";
				description = "The requirement \"" + requirementName + "\" is unmet (currently \""
						+ volRequirementStatus + "\")";
				severityType = NotificationSeverityType.MEDIUM;
			} else if (disposition == NotificationDispositionType.EXPIRED) {
				name = "Requirement Expired";
				description = "The requirement \"" + requirementName + "\" expired on "
						+ expirationDate.format(DateUtil.DATE_ONLY_FORMAT) + " and is past due";
				severityType = NotificationSeverityType.MEDIUM;
			} else if (disposition == NotificationDispositionType.WARNING) {
				name = "Requirement Nearing Expiration";
				description = "The requirement \"" + requirementName + "\" expires in " + daysUntilExpiration
						+ " days on " + expirationDate.format(DateUtil.DATE_ONLY_FORMAT);
			} else {
				continue;
			}

			Notification n = new Notification(name, description, severityType, NotificationType.SYSTEM, beginDate, null,
					null, facility, false, VOLUNTEER_PROFILE).withRefVolunteerRequirement(vr)
							.withTargetRole(facilityAdminRole).withTargetFacility(facility)
							.setUniqueIdentifier("ExpiringReq" + volunteerRequirementId);

			volNotifications.add(n);
		}
		return volNotifications;
	}

	@Override
	public int purgeExpiredNotifications() {
		return notificationDAO.purgeExpiredNotifications();
	}

}
