package org.minager.core.security;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;

import org.minager.core.entities.persistence.User;
import org.springframework.stereotype.Service;

@Service
public class TokenService {
	/** Map of connected users. */
	private static final Map<String, User> connectedUsers;

	private static final String HEADER_TOKEN = "token";

	private static final long INTERVAL_USER_CLEANING = 5;
	
	private static final long INTERVAL_USER_CLEANING_VAL = INTERVAL_USER_CLEANING * 60 * 1000;
	
	private Date lastUsersCleaning;
	
	/**
	 * Class constructor
	 */
	static {
		connectedUsers = new TreeMap<>();
	}
	
	/**
	 * Check if the token matches to a user session, and if it is still valid.
	 * 
	 * @param pToken
	 *            The token to check.
	 * @return {@code true} if the token is still valid, {@code false}
	 *         otherwise.
	 */
	public boolean isUserConnected(final String pToken) {
		boolean result = false;

		if (pToken != null && connectedUsers.containsKey(pToken)) {
			if (connectedUsers.get(pToken).getToken().isValid()) {
				result = true;
			} else {
				connectedUsers.remove(pToken);
			}
		}

		// clear all the expired sessions
		final Date now = new Date();
		if(lastUsersCleaning == null || now.getTime() - lastUsersCleaning.getTime() >= INTERVAL_USER_CLEANING_VAL) {
			new Thread(this::clearExpiredUsers).start();
			lastUsersCleaning = now;
		}
		

		return result;
	}
	
	/**
	 * Remove from the connected users map all the elements which their token is
	 * expired.
	 */
	private void clearExpiredUsers() {
		synchronized (this) {
			List<User> usersToRemove = new LinkedList<>();
			connectedUsers.entrySet().stream().forEach(user -> {
				if(!user.getValue().getToken().isValid()) {
					usersToRemove.add(user.getValue());
				}
			});
			usersToRemove.stream().map(User::getKey).forEach(connectedUsers::remove);
		}
	}
	
	/**
	 * Add the user to the connected users map.
	 * 
	 * @param pUser
	 *            The user to add.
	 */
	public void addUser(final User pUser) {
		if(connectedUsers.get(pUser.getToken().getValue()) == null) {
			connectedUsers.put(pUser.getToken().getValue(), pUser);
		}
	}
	
	/**
	 * Refresh the user token last access date in the token service.
	 * 
	 * @param pToken
	 *            The user token.
	 */
	public void refreshUserToken(final String pToken) {
		final User user = connectedUsers.get(pToken);
		if(user != null) {
			user.getToken().setLastAccess();
		}
	}
	
	/**
	 * Remove the user to the connected users map.
	 * 
	 * @param pUser
	 *            The user to remove.
	 */
	public void removeUser(final User pUser) {
		removeUser(pUser.getToken().getValue());
	}
	
	/**
	 * Remove the user associated to the token given in parameters, from the
	 * connected users map.
	 * 
	 * @param pToken
	 *            The user to delete token.
	 */
	public void removeUser(final String pToken) {
		if(pToken != null && connectedUsers.containsKey(pToken)) {
			connectedUsers.remove(pToken);
		}
	}
	
	public Optional<User> getAuthenticatedUserByToken(final HttpServletRequest pRequest) {
		return Optional.ofNullable(connectedUsers.get(pRequest.getHeader(HEADER_TOKEN)));
	}
}
