package gov.va.med.mpn.filter;

import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * The SessionTimeoutFilter monitors the duration of each user's inactivity and
 * automatically logs users out if that duration exceeds the configured threshold. 
 */
public class SessionTimeoutFilter extends OncePerRequestFilter {
	/**
	 * Conversion factor for time calculations.
	 */
	private static final long MILLISECONDS_PER_SECOND = 1000;

	/**
	 * Session attribute name for the most recent date/time the server recorded activity on the client.
	 */
	private static final String LAST_USER_ACTIVE_DATE_ATTRIBUTE = "lastUserActiveDate";

	/**
	 * Context parameter name (i.e., context-param element in web.xml) for the URL to redirect to for
	 * automatically logging the user out.
	 */
	private static final String LOGOUT_URL_PARAMETER = "logout-url";

	/**
	 * Context parameter name (i.e., context-param element in web.xml) for the list of URL prefixes which
	 * this filter should process.
	 */
	private static final String FILTERED_URL_PREFIXES_PARAMETER = "filtered-url-prefixes";
	
	private final Logger logger = LoggerFactory
			.getLogger(SessionTimeoutFilter.class);
	
	@Override
	protected void doFilterInternal(
			HttpServletRequest request,
			HttpServletResponse response, 
			FilterChain chain)
			throws ServletException, IOException {	
		
		HttpSession session = request.getSession();		
		Date lastUserActiveDate = (Date) session.getAttribute(LAST_USER_ACTIVE_DATE_ATTRIBUTE);
		Date currentDate = new Date();
		
		if(isFirstRequestForSession(lastUserActiveDate)) {
			//Initialize lastUserActiveDate.
			logger.debug("First request for session.");
			lastUserActiveDate = new Date();
			session.setAttribute(LAST_USER_ACTIVE_DATE_ATTRIBUTE, lastUserActiveDate);			
			logger.debug("Set lastUserActiveDate: " + lastUserActiveDate.getTime());
			logger.debug("Max Inactive Interval (seconds): " + session.getMaxInactiveInterval());
		}
		else if(sessionTimeExceeded(currentDate, lastUserActiveDate, session.getMaxInactiveInterval())) {
			//Initiate automatic logout.
			logger.debug("Session time exceeded. Logging out.");
			String logoutUrl = getServletContext().getInitParameter(LOGOUT_URL_PARAMETER);
			response.sendRedirect(logoutUrl);
			return;
		}
		else {
			//Reset session time.
			logger.debug("Resetting session time.");
			lastUserActiveDate = new Date();
			session.setAttribute(LAST_USER_ACTIVE_DATE_ATTRIBUTE, lastUserActiveDate);
		}		
		
		logger.debug("lastUserActiveDate: " + lastUserActiveDate.getTime());
		logger.debug("currentDate: " + currentDate.getTime());
		logger.debug("Inactive duration: " 
				+ (currentDate.getTime() - lastUserActiveDate.getTime()));			
		
		chain.doFilter(request, response);
	}

	@Override
	protected boolean shouldNotFilter(HttpServletRequest request) {
		boolean result = true;
		String urlPrefixesParameter = getServletContext().getInitParameter(FILTERED_URL_PREFIXES_PARAMETER);
		
		if(urlPrefixesParameter != null) {
			List<String> prefixes = Arrays.asList(urlPrefixesParameter.split(";"));
			String uri = request.getRequestURI();
			
			for (String prefix : prefixes) {
				if(uri.startsWith(prefix)) {
					result = false;
					break;
				}
			}
		}
		
		return result;
	}
	
	/**
	 * Determines whether this is the first request the filter has processed for this session.
	 * 
	 * @param lastUserActiveDate
	 * @return true if this is the first request the filter has processed for this session; otherwise, false.
	 */
	private boolean isFirstRequestForSession(Date lastUserActiveDate) {
		return lastUserActiveDate == null;
	}
	
	/**
	 * Determines whether the user has been inactive for too long.
	 * 
	 * @param currentDate
	 * @param lastUserActiveDate
	 * @param maxInactiveSeconds
	 * @return true if the user has been inactive for too long; otherwise, false.
	 */
	private boolean sessionTimeExceeded(
			Date currentDate, 
			Date lastUserActiveDate, 
			long maxInactiveSeconds) {
		boolean result = false;
		
		long currentTime = currentDate.getTime();
		long lastUserActiveTime = lastUserActiveDate.getTime();
		long inactiveDurationMilliSecs = currentTime - lastUserActiveTime;
		long maxInactiveMilliSecs = maxInactiveSeconds * MILLISECONDS_PER_SECOND;
		
		if(inactiveDurationMilliSecs > maxInactiveMilliSecs) {
			result = true;
		}
		
		return result;
	}
}
