package eu.eudat.logic.managers;

import eu.eudat.data.entities.Credential;
import eu.eudat.data.entities.EmailConfirmation;
import eu.eudat.data.entities.UserInfo;
import eu.eudat.data.entities.UserToken;
import eu.eudat.exceptions.emailconfirmation.HasConfirmedEmailException;
import eu.eudat.exceptions.emailconfirmation.TokenExpiredException;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.logic.services.operations.DatabaseRepository;
import eu.eudat.models.data.security.Principal;
import eu.eudat.queryable.QueryableList;
import eu.eudat.queryable.jpa.predicates.OrderByPredicate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.UUID;

@Component
public class EmailConfirmationManager {
	private ApiContext apiContext;
	private DatabaseRepository databaseRepository;

	@Autowired
	public EmailConfirmationManager(ApiContext apiContext) {
		this.apiContext = apiContext;
		this.databaseRepository = apiContext.getOperationsContext().getDatabaseRepository();
	}

	public void confirmEmail(String token) throws TokenExpiredException, HasConfirmedEmailException {
		EmailConfirmation loginConfirmationEmail = apiContext.getOperationsContext()
				.getDatabaseRepository().getLoginConfirmationEmailDao().asQueryable()
				.where((builder, root) -> builder.equal(root.get("token"), UUID.fromString(token))).getSingle();

		UserInfo user = databaseRepository.getUserInfoDao().asQueryable()
				.where((builder, root) -> builder.equal(root.get("id"), loginConfirmationEmail.getUserId())).getSingle();

		if (user.getEmail() != null)
			throw new HasConfirmedEmailException("User already has confirmed his Email.");

		if (loginConfirmationEmail.getExpiresAt().compareTo(new Date()) < 0)
			throw new TokenExpiredException("Token has expired.");

		loginConfirmationEmail.setIsConfirmed(true);

		// Checks if mail is used by another user. If it is, merges the new the old.
		Long existingUsers = databaseRepository.getUserInfoDao().asQueryable().where((builder, root) -> builder.equal(root.get("email"), loginConfirmationEmail.getEmail())).count();
		if (existingUsers > 0) {
			UserInfo oldUser = databaseRepository.getUserInfoDao().asQueryable().where((builder, root) -> builder.equal(root.get("email"), loginConfirmationEmail.getEmail())).getSingle();
			mergeNewUserToOld(user, oldUser);
			expireUserToken(user);
			databaseRepository.getLoginConfirmationEmailDao().createOrUpdate(loginConfirmationEmail);
			return;
		}

		user.setEmail(loginConfirmationEmail.getEmail());
		databaseRepository.getUserInfoDao().createOrUpdate(user);
		databaseRepository.getLoginConfirmationEmailDao().createOrUpdate(loginConfirmationEmail);
	}

	public void sendConfirmationEmail(String email, Principal principal) throws HasConfirmedEmailException {
		UserInfo user = apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().find(principal.getId());
		if (user.getEmail() != null)
			throw new HasConfirmedEmailException("User already has confirmed his Email.");

		apiContext.getUtilitiesService().getConfirmationEmailService().createConfirmationEmail(
				databaseRepository.getLoginConfirmationEmailDao(),
				apiContext.getUtilitiesService().getMailService(),
				email,
				principal.getId()
		);
	}

	private void mergeNewUserToOld(UserInfo newUser, UserInfo oldUser) {
		Credential credential = databaseRepository.getCredentialDao().asQueryable().where((builder, root) -> builder.equal(root.get("userInfo"), newUser)).getSingle();
		credential.setUserInfo(oldUser);
		databaseRepository.getCredentialDao().createOrUpdate(credential);
	}

	private void expireUserToken(UserInfo user) {
		UserToken userToken = databaseRepository.getUserTokenDao().asQueryable()
				.where((builder, root) -> builder.equal(root.get("user"), user))
				.orderBy((builder, root) -> builder.desc(root.get("issuedAt")))
				.take(1)
				.getSingle();
		userToken.setExpiresAt(new Date());
		databaseRepository.getUserTokenDao().createOrUpdate(userToken);
	}
}
