package gov.va.vss.web;

import static gov.va.vss.config.WebSecurityConfig.URI_LOGIN;

import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.CompareToBuilder;
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.context.i18n.LocaleContextHolder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.servlet.LocaleResolver;

import com.fasterxml.jackson.annotation.JsonView;

import gov.va.shared.persistence.impl.AbstractAppDAOImpl;
import gov.va.shared.service.validation.ServiceValidationException;
import gov.va.vss.config.WebSecurityConfig;
import gov.va.vss.model.facility.Kiosk;
import gov.va.vss.model.facility.Kiosk.KioskAssignmentsAndOrgsView;
import gov.va.vss.model.facility.StationParameters;
import gov.va.vss.model.lookup.Language;
import gov.va.vss.model.lookup.TemplateType;
import gov.va.vss.model.requirement.AbstractVolunteerRequirement;
import gov.va.vss.model.requirement.VolunteerRequirement;
import gov.va.vss.model.time.WorkEntry;
import gov.va.vss.model.volunteer.Volunteer;
import gov.va.vss.model.volunteer.VolunteerAssignment;
import gov.va.vss.model.volunteer.VolunteerOrganization;
import gov.va.vss.persistence.dao.TimeSummary;
import gov.va.vss.service.NotificationDispositionType;
import gov.va.vss.service.VelocityService;
import gov.va.vss.service.impl.RequirementServiceImpl;
import gov.va.vss.web.validation.ValidationException;

@Controller
@SessionAttributes(value = { "command" })
public class KioskController extends AbstractKioskController {
	private static final Logger log = LoggerFactory.getLogger(KioskController.class);

	public static final String SESSION_ATTR_KIOSK_ID = "kioskId";
	private static final String VIEW_KIOSK_CHANGE = "kioskChange";
	public static final String BREADCRUMB_HOME = "Home";
	public static final String URL_HOME = "/home.htm";
	public static final String VIEW_HOME = "home";

	@Autowired
	private VelocityService velocityService;
	@Value("${session.kiosk.expirationSeconds}")
	private int sessionExpirationSeconds;
	@Value("${printerStatusCheckFailureMinutes}")
	private int printerStatusCheckFailureMinutes;
	@Value("${maxRequirementWarnings}")
	private int maxRequirementWarnings;

	@Autowired
	private LocaleResolver localeResolver;
	@Autowired
	private VolunteerValidator volunteerValidator;

	@RequestMapping(value = URI_LOGIN, method = RequestMethod.GET)
	public String loginPage(@RequestParam(required = false) String error, @RequestParam(required = false) Long kioskId,
			@RequestParam(required = false) Boolean thankYou,
			@CookieValue(required = false, name = "vssKioskId") Long cookieKioskId, ModelMap model,
			HttpServletResponse response) {
		String locale = LocaleContextHolder.getLocale().getLanguage();

		if (kioskId == null) {
			if (cookieKioskId == null) {
				return "redirect:/selectKiosk.htm";
			} else {
				if (kioskDAO.findByPrimaryKey(cookieKioskId) == null) {
					return "kioskMissing";
				} else {
					return "redirect:/login.htm?kioskId=" + cookieKioskId //
							+ (error != null ? "&error=" + error : "") //
							+ (thankYou != null ? "&thankYou=" + thankYou : "");
				}
			}
		} else {
			if (cookieKioskId == null || cookieKioskId.equals(kioskId) == false) {
				model.addAttribute("showBookmarkMsg", true);
				/*
				 * Save a cookie so that we don't have to pass it thru the
				 * spring login page, and so that we can retrieve it if they log
				 * out - CPB
				 */
				response.addCookie(getCookie(kioskId));
			}
		}

		Kiosk kiosk = kioskDAO.findByPrimaryKey(kioskId);
		if (kiosk == null) {
			return "kioskMissing";
		}

		if (StringUtils.isNotBlank(error)) {
			model.addAttribute("errorMessage",
					velocityService.mergeTemplateIntoString("login.error." + error + "." + locale));
		}

		StationParameters stationParameters = kiosk.getFacility().getStationParameters();
		model.addAttribute("introductoryText",
				"en".equals(locale) || StringUtils.isBlank(stationParameters.getAlternateLanguageIntroText())
						? stationParameters.getIntroductoryText() : stationParameters.getAlternateLanguageIntroText());
		model.addAttribute("globalIntroText",
				velocityService.mergeTemplateIntoString("kiosk.globalIntroText" + "." + locale));
		return "login";
	}

	public Cookie getCookie(Long kioskId) {
		Cookie cookie = new Cookie("vssKioskId", String.valueOf(kioskId));
		cookie.setHttpOnly(true);
		// 10 years ought to do it
		cookie.setMaxAge(60 * 60 * 24 * 365 * 10);
		return cookie;
	}

	@RequestMapping(value = WebSecurityConfig.URI_LOGOUT, method = RequestMethod.GET)
	public String logoutPage(@RequestParam(required = false) Boolean thankYou, HttpServletRequest request,
			HttpServletResponse response) {
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		if (auth != null) {
			new SecurityContextLogoutHandler().logout(request, response, auth);
		}

		return "redirect:/login.htm?logout" + (thankYou != null && thankYou ? "&thankYou=true" : "");
	}

	@RequestMapping("/index.htm")
	public String index(ModelMap model, HttpServletRequest request, HttpServletResponse response, HttpSession session,
			@CookieValue(name = "vssKioskId") long cookieKioskId) {
		session.setMaxInactiveInterval(sessionExpirationSeconds);
		Kiosk kiosk = kioskDAO.findRequiredByPrimaryKey(cookieKioskId);
		setFacilityContext(kiosk.getFacility());

		Volunteer v = volunteerDAO.findRequiredByPrimaryKey(getCurrentUser().getId());

		Language preferredLanguage = v.getPreferredLanguage();
		if (preferredLanguage != null)
			localeResolver.setLocale(request, response, Locale.forLanguageTag(preferredLanguage.getCulture()));

		ZonedDateTime lastPrinterStatusCheck = kiosk.getLastPrinterStatusCheck();
		if (lastPrinterStatusCheck == null || lastPrinterStatusCheck
				.isBefore(ZonedDateTime.now().minusMinutes(printerStatusCheckFailureMinutes))) {
			model.addAttribute("printerStatusCheckFailure", true);
		} else if (kiosk.getLastPrinterError() != null) {
			model.addAttribute("printerError", kiosk.getLastPrinterError());
		}

		return "redirect:/postTime.htm";
	}

	@RequestMapping("/help.htm")
	public String help(ModelMap model) {
		String locale = LocaleContextHolder.getLocale().getLanguage();
		model.put("helpContent",
				velocityService.mergeTemplateIntoString(TemplateType.HELP_CONTENT.getName() + "." + locale));
		return "help";
	}

	/**
	 * Renew the session, so it will not timeout, ajax call
	 */
	@RequestMapping("/keepAlive")
	public @ResponseBody boolean keepAlive() {
		log.debug("Received request to keep-alive the session...");
		return true;
	}

	@RequestMapping(URL_HOME)
	public String home(ModelMap model) {
		String locale = LocaleContextHolder.getLocale().getLanguage();
		model.put("homepageContent",
				velocityService.mergeTemplateIntoString(TemplateType.HOMEPAGE_CONTENT.getName() + "." + locale));
		model.put("homepageAnnouncement",
				velocityService.mergeTemplateIntoString(TemplateType.HOMEPAGE_ANNOUNCEMENT.getName() + "." + locale));

		// Already a station in session. Do nothing, redirect to sitemap.
		return VIEW_HOME;
	}

	@RequestMapping("/postTime/assignmentsAndOrgs")
	@JsonView(KioskAssignmentsAndOrgsView.Combined.class)
	public @ResponseBody Map<String, Object> getVolunteerDetails() {
		Map<String, Object> results = new HashMap<>();
		Long volunteerId = getCurrentUser().getId();

		/*
		 * These block time posting, either for specific assignments or for all
		 * assignments at that facility.
		 */
		List<AbstractVolunteerRequirement> unmetRequirements = volunteerRequirementDAO
				.findUnmetRequirements(volunteerId, getFacilityContextId());
		LocalDate today = getTodayAtFacility();
		/* Warnings to show above the timeposting in any circumstance */
		List<VolunteerRequirement> expiringSoonReqs = volunteerRequirementDAO
				.findForExpiringRequirements(getFacilityContextId(), volunteerId, maxRequirementWarnings);
		expiringSoonReqs = expiringSoonReqs.stream()
				.filter(p -> notificationService.getNotificationDisposition(today, p) == NotificationDispositionType.WARNING)
				.collect(Collectors.toList());
		results.put("warningRequirements", expiringSoonReqs);

		List<VolunteerAssignment> assignments = volunteerAssignmentDAO.findByCriteria(volunteerId, null, null, null,
				getFacilityContextId());
		results.put("assignments", assignments);

		results.put("unmetRequirements", RequirementServiceImpl.getRequirementsByScope(unmetRequirements, assignments));

		List<VolunteerOrganization> volOrgs = volunteerOrganizationDAO.findByCriteria(volunteerId, null, null,
				getFacilityContextId());
		results.put("organizations", volOrgs);

		TimeSummary summary = volunteerDAO.getTimeSummary(volunteerId, getFacilityTimeZone());
		results.put("timeSummary", summary);

		List<WorkEntry> todayEntries = workEntryDAO.findByCriteria(volunteerId, null, getFacilityContextId(), null,
				getTodayAtFacility(), null);
		results.put("todayEntries", todayEntries);

		return results;
	}

	@RequestMapping("/postTime.htm")
	public String postTime(ModelMap model) {
		Long volunteerId = getCurrentUser().getId();
		Volunteer volunteer = volunteerDAO.findRequiredByPrimaryKey(volunteerId);
		model.put("volunteer", volunteer);
		volunteer.getVolunteerOrganizations().stream()
				.filter(p -> p.getOrganization().equals(volunteer.getPrimaryOrganization()) && p.isActive()).findFirst()
				.ifPresent(q -> model.put("primaryVolOrganization", q));
		return "postTime";
	}

	@RequestMapping("/postTime/submit")
	public @ResponseBody boolean postTimeSubmit(@RequestParam int numEntries, HttpServletRequest request)
			throws ServletRequestBindingException, ServiceValidationException {
		long volunteerId = getCurrentUser().getId();
		LocalDate today = getTodayAtFacility();
		long facilityId = getFacilityContextId();

		Set<Long> assignmentIds = new HashSet<>();
		Set<Long> organizationIds = new HashSet<>();
		for (int i = 0; i < numEntries; i++) {
			assignmentIds.add(ServletRequestUtils.getRequiredLongParameter(request, "assignmentId" + i));
			organizationIds.add(ServletRequestUtils.getRequiredLongParameter(request, "organizationId" + i));
		}
		Map<Long, VolunteerAssignment> assignmentMap = volunteerAssignmentDAO.findRequiredByPrimaryKeys(assignmentIds);
		Map<Long, VolunteerOrganization> organizationsMap = volunteerOrganizationDAO
				.findRequiredByPrimaryKeys(organizationIds);

		List<WorkEntry> worksheet = new ArrayList<>();
		for (int i = 0; i < numEntries; i++) {
			int hours = ServletRequestUtils.getRequiredIntParameter(request, "hours" + i);
			int minutes = ServletRequestUtils.getRequiredIntParameter(request, "minutes" + i);
			long assignmentId = ServletRequestUtils.getRequiredLongParameter(request, "assignmentId" + i);
			long organizationId = ServletRequestUtils.getRequiredLongParameter(request, "organizationId" + i);
			double finalHours = hours + minutes / 60.0;

			VolunteerAssignment va = assignmentMap.get(assignmentId);
			VolunteerOrganization o = organizationsMap.get(organizationId);
			worksheet.add(new WorkEntry(va, o.getOrganization(), today, finalHours));
		}

		workEntryService.saveOrUpdateMultipleForVolunteerAndDayAndFacility(worksheet, volunteerId, facilityId, today);

		return true;
	}

	@RequestMapping(value = "/selectKiosk.htm", method = RequestMethod.GET)
	public String selectStation(ModelMap model) {

		SortedSet<Kiosk> unregisteredKiosksByFacility = new TreeSet<>(new Comparator<Kiosk>() {
			@Override
			public int compare(Kiosk o1, Kiosk o2) {
				if (o1.equals(o2))
					return 0;

				return new CompareToBuilder().append(o1.getFacility(), o2.getFacility()).append(o1, o2)
						.toComparison() > 0 ? 1 : -1;
			}
		});
		unregisteredKiosksByFacility.addAll(kioskDAO.findByCriteria(false));

		model.put("kioskList", unregisteredKiosksByFacility);
		model.put("cancelAllowed", false);
		return VIEW_KIOSK_CHANGE;
	}

	@RequestMapping(value = "/selectKiosk.htm", method = RequestMethod.POST)
	public String processSelectStation(@RequestParam long kioskId, HttpServletResponse response) {
		kioskService.registerKiosk(kioskId);
		return "redirect:" + URI_LOGIN + "?kioskId=" + kioskId;
	}

	@RequestMapping("/mealTicketPrint.htm")
	public String mealTicketPrintCheck(ModelMap model, HttpServletRequest request, HttpSession session) {
		ZonedDateTime requestTime = ZonedDateTime.now();
		int numTicketsDue = mealTicketService.processMealTicketsForTodayByKiosk(requestTime, getKioskId(request),
				getCurrentUser().getId(), false);
		if (numTicketsDue == 0) {
			// userNotifier.notifyUserOnce(request, "mealTicket.noneDue");
			Locale locale = LocaleContextHolder.getLocale();
			return "redirect:/logout.htm?thankYou=true&locale=" + locale.getLanguage();
		}
		model.put("numMealTickets", numTicketsDue);
		session.setAttribute("mealTicketRequestTime", requestTime);
		return "mealTicketPrint";
	}

	@RequestMapping("/printTickets.htm")
	public String mealTicketPrintSubmit(ModelMap model, HttpServletRequest request, HttpSession session) {
		ZonedDateTime requestTime = (ZonedDateTime) session.getAttribute("mealTicketRequestTime");
		if (requestTime == null) {
			requestTime = ZonedDateTime.now();
		} else {
			session.removeAttribute("mealTicketRequestTime");
		}

		int numTicketsPrinted = mealTicketService.processMealTicketsForTodayByKiosk(requestTime, getKioskId(request),
				getCurrentUser().getId(), true);
		userNotifier.notifyUserOnce(request, "mealTicket.printSuccessful", new Object[] { numTicketsPrinted });
		Locale locale = LocaleContextHolder.getLocale();
		return "redirect:/logout.htm?thankYou=true&locale=" + locale.getLanguage();
	}

	@RequestMapping("/volunteerEdit.htm")
	public String volunteerEdit(ModelMap model, HttpServletRequest request, HttpSession session) {
		Long volunteerId = getCurrentUser().getId();
		Volunteer volunteer = volunteerDAO.findRequiredByPrimaryKey(volunteerId);

		VolunteerCommand command = new VolunteerCommand(volunteer);
		model.put(DEFAULT_COMMAND_NAME, command);
		createReferenceData(model);

		return "editVolunteer";
	}

	private void createReferenceData(ModelMap model) {
		model.put("allGenders", genderDAO.findAllSorted());
	}

	@RequestMapping("/volunteerSubmit.htm")
	public String volunteerSubmit(@ModelAttribute(DEFAULT_COMMAND_NAME) VolunteerCommand command, BindingResult result,
			SessionStatus status, ModelMap model, HttpServletRequest request, HttpServletResponse response)
			throws ValidationException {
		Volunteer v = command.getVolunteer();

		volunteerValidator.validate(command, result, false, "volunteer");
		boolean hasErrors = result.hasErrors();

		if (!hasErrors) {
			try {
				v = volunteerService.updateBasicFields(v.getId(), v.getFirstName(), v.getMiddleName(), v.getLastName(),
						v.getSuffix(), v.getNickname(), (v.getGender() != null ? v.getGender().getId() : null),
						v.getDateOfBirth(), v.getEmergencyContactName(), v.getEmergencyContactRelationship(),
						v.getEmergencyContactPhone(), v.getEmergencyContactPhoneAlt(), v.getAddressLine1(),
						v.getAddressLine2(), v.getCity(), v.getState() == null ? null : v.getState().getId(),
						v.getZip(), v.getEmail(), v.getPhone(), v.getPhoneAlt(), v.getPhoneAlt2(), true, true);
				// if volunteer is terminated, logout
				if (v.getStatus().getLookupType().isTerminated()) {
					Authentication auth = SecurityContextHolder.getContext().getAuthentication();
					if (auth != null) {
						new SecurityContextLogoutHandler().logout(request, response, auth);
					}

					return "redirect:/login.htm?logout";

				}
				userNotifier.notifyUserOnceWithMessage(request, getMessage("volunteer.update.success"));
			} catch (ServiceValidationException e) {
				webValidationService.handle(e, result);
				hasErrors = true;
			}
		}

		if (hasErrors) {
			createReferenceData(model);
			return "editVolunteer";
		} else {
			// status.setComplete();
			return "redirect:/volunteerEdit.htm?id=" + v.getId();
		}
	}

	@RequestMapping("/flushEveryOp")
	public @ResponseBody boolean flushEveryOperationTrue() {
		AbstractAppDAOImpl.FLUSH_EVERY_OPERATION = true;
		return true;
	}

	@RequestMapping("/noFlushEveryOp")
	public @ResponseBody boolean flushEveryOperationFalse() {
		AbstractAppDAOImpl.FLUSH_EVERY_OPERATION = false;
		return true;
	}

}
