package eu.dnetlib.goldoa.service;


import eu.dnetlib.goldoa.domain.*;
import eu.dnetlib.goldoa.service.dao.UserDAO;
import eu.dnetlib.goldoa.service.utils.EmailUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.mail.MessagingException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;


/**
 * Created by antleb on 3/4/15.
 */
@Service("userManager")
@Transactional
public class UserManagerImpl implements UserManager {

	@Autowired
	private UserDAO userDAO;

	@Autowired
	private EmailUtils emailUtils;

	@Autowired
	ExecutorService executorService;

	private int tokenTTL = 30;

	private Log LOGGER = LogFactory.getLog(UserManagerImpl.class);

	public static int generateId(Affiliation affiliation) {
		StringBuilder sb = new StringBuilder();

		if (affiliation.getUsers().get(0) != null)
			sb.append(affiliation.getUsers().get(0).getId());
		if (affiliation.getOrganization() != null)
			sb.append(affiliation.getOrganization().getId());

		return DigestUtils.md5Hex(sb.toString()).hashCode();
	}

	@Override
	public User getById(String personId) throws PersonManagerException {
		return userDAO.getUserById(personId);
	}

	@Override
	public User getByEmail(final String email) throws PersonManagerException {
		User user = userDAO.getUserByEmail(email);
		if(user == null)
			throw new PersonManagerException(PersonManagerException.ErrorCause.NOT_EXISTS);
		return user;
	}

	@Override
	// register or update
	public User saveUser(final User user) throws PersonManagerException {
		final List<UserRole> previousRoles;

		if (user.getId() == null) {
			user.setId(generateId(user));
			user.setSource("portal");

			previousRoles = new ArrayList<UserRole>();
		} else {
			previousRoles = user.getRoles();
		}
		
		userDAO.saveUser(user);

		final List<User> moderators = userDAO.getModerators();

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					List<Role> roles = new ArrayList<>();
					for (UserRole pRole : user.getRoles()) {
						//!previousRoles.contains(pRole) &&
						if ( !pRole.isApproved())
							roles.add(pRole.getPk().getRole());
					}

					if (roles.size() > 0) {
						emailUtils.sendUserRoleRequestedEmail(user, roles);

						for (User moderator : moderators)
							emailUtils.sendModeratorRoleRequestedEmail(moderator, user, roles);
					}
				} catch (MessagingException e) {
					e.printStackTrace();
				}
			}
		});

		return user;
	}

	@Override
	public void register(final User user) throws PersonManagerException {
		final String token = UUID.randomUUID().toString();
		
		try {
			User u = userDAO.getUserByEmail(user.getEmail());
			if (u != null)
				throw new PersonManagerException(PersonManagerException.ErrorCause.ALREADY_EXISTS);
		} catch (PersonManagerException e) {
			throw e;
		} catch (RuntimeException e) {
			e.printStackTrace();
			throw e;
		}

		user.setPassword(org.springframework.util.DigestUtils.md5DigestAsHex( user.getPassword().getBytes(Charset.forName("UTF-8"))));
		this.saveUser(user);
		userDAO.saveToken(user.getEmail(), token, tokenTTL);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					emailUtils.sendActivationEmail(user, token);
				} catch (MessagingException e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public User login(String email, String password) throws PersonManagerException {

		try {
			if (userDAO.verifyLogin(email, password)) {
				User user = getByEmail(email);
				if (user.isActive()) {
					return user;
				}
				else
					throw new PersonManagerException(PersonManagerException.ErrorCause.NOT_ACTIVATED);
			} else {
				throw new PersonManagerException(PersonManagerException.ErrorCause.WRONG_EMAIL_OR_PASSWORD);
			}
		} catch (PersonManagerException e) {
			throw e;
		} catch (Exception e) {
			LOGGER.error("Error logging in", e);
		}
		return null;
	}

	@Override
	public User activate(final String email, final String token) throws PersonManagerException {

		if (!userDAO.verifyToken(email, token))
			throw new PersonManagerException(PersonManagerException.ErrorCause.NOT_EXISTS);
		else {
			User user = userDAO.getUserByEmail(email);
			userDAO.activateUser(user.getEmail());
			userDAO.deleteToken(user.getEmail(), token);
		}
		return getByEmail(email);
	}

	@Override
	public void resetPassword(final String email) throws PersonManagerException {
		SecureRandom random = new SecureRandom();
		final String newPassword = new BigInteger(50, random).toString(32);

		if (!userDAO.updatePassword(newPassword, email)) {
			throw new PersonManagerException(PersonManagerException.ErrorCause.NOT_EXISTS);
		}
		final User user = userDAO.getUserByEmail(email);
		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					emailUtils.sendResetPasswordEmail(user, newPassword);
				} catch (MessagingException e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public List<Role> getRoles() {
		return userDAO.getRoles();
	}

	@Override
	public List<User> getModerators() {
		return userDAO.getModerators();
	}

	@Override
	public List<User> getAccountingOfficers() {
		return userDAO.getAccountingOfficers();
	}

	@Override
	public void activateUser(String email) {
		userDAO.activateUser(email);
	}

	@Override
	public void activateUsers(List<String> emails) {
		for (String email : emails) {
			userDAO.activateUser(email);
		}
	}

	@Override
	public void deactivateUser(String email) {
		userDAO.deactivateUser(email);
	}

	@Override
	public void deactivateUsers(List<String> emails) {
		for (String email : emails) {
			userDAO.deactivateUser(email);
		}
	}

	@Override
	public void acceptUserRole(final String email, final String roleId) {
		userDAO.acceptUserRole(email, roleId);
		final User user = userDAO.getUserByEmail(email);
		final Role role = userDAO.getRole(roleId);
		final List<User> moderators = getModerators();
		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					emailUtils.sendUserRoleAcceptedEmail(user, role);
					for (User moderator : moderators)
						emailUtils.sendModeratorRoleAcceptedEmail(moderator, user, role);
				} catch (MessagingException e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public void rejectRole(final String email, final String roleId) {
		userDAO.rejectUserRole(email, roleId);

		final User user = userDAO.getUserByEmail(email);
		final Role role = getRole(roleId);
		final List<User> moderators = getModerators();

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					emailUtils.sendUserRoleRejectedEmail(user, role);
					for (User moderator : moderators)
						emailUtils.sendModeratorRoleRejectedEmail(moderator, user, role);
				} catch (MessagingException e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public List<User> getUsers() {
		return userDAO.getUsers();
	}

	@Override
	public String generateId(User user) {
		StringBuilder sb = new StringBuilder();

		sb.append(user.getFirstname()).append(user.getLastname()).append(user.getEmail());
		return "portal::" + DigestUtils.md5Hex(sb.toString());
	}

	public int getTokenTTL() {
		return tokenTTL;
	}

	public void setTokenTTL(int tokenTTL) {
		this.tokenTTL = tokenTTL;
	}

	@Override
	public Role getRole(String roleId){
		return userDAO.getRole(roleId);
	}

	@Override
	public List<Role> getUserRoles(String email){
		return userDAO.getUserRoles(email);
	}

	@Override
	public User createInActiveResearcher(String firstname, String lastname, String email, Organization organization) throws OrganizationManagerException, PersonManagerException {

		if(firstname == null || lastname == null || email == null)
			throw new PersonManagerException("Empty firstname or lastname or email");
		return userDAO.createInActiveResearcher(firstname,lastname,email,organization);
	}

}
