package eu.dnetlib.goldoa.service;

import eu.dnetlib.goldoa.domain.Affiliation;
import eu.dnetlib.goldoa.domain.OrganizationManagerException;
import eu.dnetlib.goldoa.domain.Person;
import eu.dnetlib.goldoa.domain.PersonManagerException;
import eu.dnetlib.goldoa.domain.PersonRole;
import eu.dnetlib.goldoa.domain.Project;
import eu.dnetlib.goldoa.domain.Role;
import eu.dnetlib.goldoa.service.dao.OrganizationDAO;
import eu.dnetlib.goldoa.service.dao.PersonDAO;
import eu.dnetlib.goldoa.service.dao.ProjectDAO;
import eu.dnetlib.goldoa.service.utils.EmailUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.transaction.annotation.Transactional;

import javax.mail.MessagingException;
import java.math.BigInteger;
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.
 */
@Transactional
public class PersonManagerImpl implements PersonManager {

	@Autowired
	private PersonDAO personDAO;
	@Autowired
	private ProjectDAO projectDAO;
	@Autowired
	private OrganizationDAO organizationDAO;
	@Autowired
	private PublisherManager publisherManager;
	@Autowired
	private OrganizationManager organizationManager;
	@Autowired
	private EmailUtils emailUtils;
	@Autowired
	ExecutorService executorService;

	private int tokenTTL = 30;

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

		if (affiliation.getPerson() != null)
			sb.append(affiliation.getPerson().getId());
		if (affiliation.getOrganization() != null)
			sb.append(affiliation.getOrganization().getId());

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

	@Override
	public Person getById(String personId) throws PersonManagerException {
		try {
			Person person = personDAO.getPersonById(personId);

			loadPersonRelations(person);

			return person;
		} catch (EmptyResultDataAccessException e) {
			throw new PersonManagerException(PersonManagerException.ErrorCause.NOT_EXISTS);
		}
	}

	@Override
	public Person getByEmail(final String email) throws PersonManagerException {
		try {
			Person person = personDAO.getPersonByEmail(email);

			loadPersonRelations(person);

			return person;
		} catch (EmptyResultDataAccessException e) {
			throw new PersonManagerException(PersonManagerException.ErrorCause.NOT_EXISTS);
		}
	}

	@Override
	public Person savePerson(final Person person) throws PersonManagerException {
		final List<PersonRole> previousRoles;

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

			previousRoles = new ArrayList<PersonRole>();
		} else {
			previousRoles = personDAO.getPersonById(person.getId()).getRoles();
		}

		personDAO.savePerson(person);
		deletePersonRelations(person);
		savePersonRelations(person);

		loadPersonRelations(person);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				try {
					List<Role> roles = new ArrayList<Role>();

					for (PersonRole pRole : person.getRoles()) {
						if (!previousRoles.contains(pRole) && !pRole.isApproved())
							roles.add(pRole.getRole());
					}

					if (roles.size() > 0) {
						List<Person> moderators = getModerators();

						emailUtils.sendUserRoleRequestedEmail(person, roles);

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

		return person;
	}

	@Override
	public void register(final Person person) throws PersonManagerException {
		final String token = UUID.randomUUID().toString();

		try {
			personDAO.getPersonByEmail(person.getEmail());
			throw new PersonManagerException(PersonManagerException.ErrorCause.ALREADY_EXISTS);
		} catch (EmptyResultDataAccessException e) {
		} catch (PersonManagerException e) {
			throw e;
		} catch (RuntimeException e) {
			e.printStackTrace();
			throw e;
		}

		this.savePerson(person);
		personDAO.saveToken(person.getId(), token, tokenTTL);

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

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

		if (personDAO.verifyLogin(email, password)) {
			Person person = getByEmail(email);

			if (person.isActive())
				return person;
			else
				throw new PersonManagerException(PersonManagerException.ErrorCause.NOT_ACTIVATED);
		} else {
			throw new PersonManagerException(PersonManagerException.ErrorCause.WRONG_EMAIL_OR_PASSWORD);
		}
	}

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

		if (!personDAO.verifyToken(email, token))
			throw new PersonManagerException(PersonManagerException.ErrorCause.NOT_EXISTS);
		else {
			Person person = personDAO.getPersonByEmail(email);

			personDAO.activateUser(person.getId());
			personDAO.deleteToken(person.getId(), 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 (!personDAO.updatePassword(newPassword, email)) {
			throw new PersonManagerException(PersonManagerException.ErrorCause.NOT_EXISTS);
		}

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				Person person = personDAO.getPersonByEmail(email);

				try {
					emailUtils.sendResetPasswordEmail(person, newPassword);
				} catch (MessagingException e) {
					e.printStackTrace();
				}
			}
		});
	}

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

	@Override
	public List<Person> getModerators() {
		return personDAO.getModerators();
	}

	@Override
	public List<Person> getAccountingOfficers() {
		return personDAO.getAccountingOfficers();
	}

	@Override
	public void activateUser(String userId) {
		personDAO.activateUser(userId);
	}

	@Override
	public void activateUsers(List<String> userIds) {
		for (String userId : userIds) {
			personDAO.activateUser(userId);
		}
	}

	@Override
	public void deactivateUser(String userId) {
		personDAO.deactivateUser(userId);
	}

	@Override
	public void deactivateUsers(List<String> userIds) {
		for (String userId : userIds) {
			personDAO.deactivateUser(userId);
		}
	}

	@Override
	public void acceptUserRole(final String userId, final String roleId) {
		personDAO.acceptUserRole(userId, roleId);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				Person person = personDAO.getPersonById(userId);
				Role role = personDAO.getRole(roleId);
				List<Person> moderators = getModerators();

				try {
					emailUtils.sendUserRoleAcceptedEmail(person, role);

					for (Person moderator : moderators)
						emailUtils.sendModeratorRoleAcceptedEmail(moderator, person, role);
				} catch (MessagingException e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public void rejectRole(final String userId, final String roleId) {
		personDAO.rejectUserRole(userId, roleId);

		executorService.submit(new Runnable() {
			@Override
			public void run() {
				Person person = personDAO.getPersonById(userId);
				Role role = personDAO.getRole(roleId);
				List<Person> moderators = getModerators();

				try {
					emailUtils.sendUserRoleRejectedEmail(person, role);

					for (Person moderator : moderators)
						emailUtils.sendModeratorRoleRejectedEmail(moderator, person, role);
				} catch (MessagingException e) {
					e.printStackTrace();
				}
			}
		});
	}

	@Override
	public List<Person> getUsers() {
		List<Person> persons = personDAO.getUsers();

		for (Person person : persons) {
			loadPersonRelations(person);
		}

		return persons;
	}

	@Override
	public String generateId(Person person) {
		StringBuilder sb = new StringBuilder();

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

	private void deletePersonRelations(final Person person) {
		personDAO.deleteAffiliations(person.getId());
		personDAO.deletePersonRoles(person.getId());
		personDAO.removeProjectCoordinators(person.getId());
	}

	private void savePersonRelations(final Person person) {
		List<String> projectIds = new ArrayList<String>();

		if (person.getCoordinatedProjects() != null)
			for (Project project : person.getCoordinatedProjects())
				projectIds.add(project.getId());

		if (person.getPublisher() != null && person.getPublisher().getId() == null)
			publisherManager.savePublisher(person.getPublisher());

		for (Affiliation affiliation : person.getAffiliations()) {
			if (affiliation.getOrganization() != null && affiliation.getOrganization().getId() == null)
				try {
					organizationManager.saveOrganization(affiliation.getOrganization());
				} catch (OrganizationManagerException e) {
					e.printStackTrace();
				}
		}

		personDAO.saveProjectCoordinators(person.getId(), projectIds);
		personDAO.savePersonRoles(person.getId(), person.getRoles());
		personDAO.saveAffiliations(person, person.getAffiliations());


	}

	private void loadPersonRelations(Person person) {

		if (person.getPublisher() != null && person.getPublisher().getId() != null) {
			person.setPublisher(publisherManager.getPublisher(person.getPublisher().getId()));
		} else {
			person.setPublisher(null);
		}

		person.setCoordinatedProjects(projectDAO.getProjectsCoordinatedBy(person.getId()));

		if (person.getAffiliations() != null) {
			for (Affiliation affiliation : person.getAffiliations()) {
				if (affiliation.getOrganization() != null)
					affiliation.setOrganization(organizationDAO.getOrganization(affiliation.getOrganization().getId()));
			}
		}

		if (person.getRoles() != null)
			for (int i = 0; i < person.getRoles().size(); i++) {
				person.getRoles().get(i).setRole(personDAO.getRole(person.getRoles().get(i).getRole().getId()));
			}
	}

	public int getTokenTTL() {
		return tokenTTL;
	}

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