package eu.dnetlib.goldoa.service;

import eu.dnetlib.goldoa.domain.*;
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.dao.PublisherDAO;
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;
	}
}
