package eu.eudat.logic.managers;

import eu.eudat.data.entities.DMP;
import eu.eudat.data.entities.UserDMP;
import eu.eudat.data.entities.UserInfo;
import eu.eudat.data.query.items.item.userinfo.UserInfoRequestItem;
import eu.eudat.exceptions.security.UnauthorisedException;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.logic.utilities.helpers.StreamDistinctBy;
import eu.eudat.models.data.invitation.Invitation;
import eu.eudat.models.data.invitation.Properties;
import eu.eudat.models.data.security.Principal;
import eu.eudat.models.data.userinfo.UserInfoInvitationModel;
import eu.eudat.queryable.QueryableList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class InvitationsManager {

    private ApiContext apiContext;
    private DataManagementPlanManager dataManagementPlanManager;

    @Autowired
    public InvitationsManager(ApiContext apiContext, DataManagementPlanManager dataManagementPlanManager) {
        this.apiContext = apiContext;
        this.dataManagementPlanManager = dataManagementPlanManager;
    }

    public void inviteUsers(Invitation invitation, Principal principal) throws Exception {
        UserInfo principalUser = new UserInfo();
        principalUser.setId(principal.getId());
        invitation.getUsers().stream().filter(item -> item.getId() == null).forEach(item -> {
			UserInfo existingUser = apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().asQueryable().where((builder, root) -> builder.equal(root.get("email"), item.getEmail())).getSingleOrDefault();
			if (existingUser != null) {
				item.setId(existingUser.getId());
			}
        });
        List<UserInfoInvitationModel> alreadySignedInUsers = invitation.getUsers().stream().filter(item -> item.getId() != null).collect(Collectors.toList());
        List<UserInfo> alreadySignedInUsersEntities = alreadySignedInUsers.stream().map(UserInfoInvitationModel::toDataModel).collect(Collectors.toList());
        List<UserDMP> userInfoToUserDmp = new LinkedList<>();
        for (UserInfo userInfo : alreadySignedInUsersEntities) {
            UserDMP userDMP = new UserDMP();
            userDMP.setUser(userInfo);
            userDMP.setRole(invitation.getRole());
            userInfoToUserDmp.add(userDMP);
            /*if (!apiContext.getOperationsContext().getDatabaseRepository().getUserAssociationDao().areAssociated(principalUser, userInfo)) {
                UserAssociation userAssociation = new UserAssociation();
                userAssociation.setFirstUser(principalUser);
                userAssociation.setSecondUser(userInfo);
                apiContext.getOperationsContext().getDatabaseRepository().getUserAssociationDao().createOrUpdate(userAssociation);
            }*/
        }
        DMP dataManagementPlan = apiContext.getOperationsContext().getDatabaseRepository().getDmpDao().find(invitation.getDataManagementPlan());
        apiContext.getUtilitiesService().getInvitationService().createInvitations(apiContext.getOperationsContext().getDatabaseRepository().getInvitationDao(), apiContext.getUtilitiesService().getMailService(), invitation.getUsers().stream().map(UserInfoInvitationModel::toDataModel).collect(Collectors.toList()), dataManagementPlan, invitation.getRole(), principalUser);
        apiContext.getUtilitiesService().getInvitationService().assignToDmp(apiContext.getOperationsContext().getDatabaseRepository().getDmpDao(), userInfoToUserDmp, dataManagementPlan);
    }

    public List<UserInfoInvitationModel> getUsers(Principal principal) throws InstantiationException, IllegalAccessException {
        /*UserInfo principalUser = apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().find(principal.getId());
        List<UserInfo> users = apiContext.getOperationsContext().getDatabaseRepository().getUserAssociationDao().getAssociated(principalUser).stream().map(userAssociation -> {
            if (userAssociation.getFirstUser().getId().equals(principal.getId())) {
                return userAssociation.getSecondUser();
            } else {
                return userAssociation.getFirstUser();
            }
        }).collect(Collectors.toList());*/
        List<UserInfo> users = apiContext.getOperationsContext().getDatabaseRepository().getDmpDao()
                                         .getAuthenticated(apiContext.getOperationsContext().getDatabaseRepository().getDmpDao().asQueryable()
                                                 .where(((builder, root) -> builder.notEqual(root.get("status"), DMP.DMPStatus.DELETED.getValue()))), principal.getId(), Stream.of(0, 1).collect(Collectors.toList()))
                                         .toList().stream().map(DMP::getUsers).flatMap(Collection::stream).map(UserDMP::getUser)
                                         .filter(userInfo -> !userInfo.getId().equals(principal.getId())).filter(StreamDistinctBy.distinctByKey(UserInfo::getId)).collect(Collectors.toList());
        List<UserInfoInvitationModel> userModels = users.stream().map(userInfo -> new UserInfoInvitationModel().fromDataModel(userInfo)).collect(Collectors.toList());
        return userModels;
    }

    public List<UserInfoInvitationModel> getUsersWithCriteria(Principal principal, UserInfoRequestItem userInfoRequestItem) throws IllegalAccessException, InstantiationException {
        List<UserInfo> users = apiContext.getOperationsContext().getDatabaseRepository().getDmpDao()
                .getAuthenticated(apiContext.getOperationsContext().getDatabaseRepository().getDmpDao().asQueryable()
                        .where(((builder, root) ->
                                builder.notEqual(root.get("status"), DMP.DMPStatus.DELETED.getValue()))), principal.getId(), Stream.of(0, 1).collect(Collectors.toList()))
                .toList().stream().map(DMP::getUsers).flatMap(Collection::stream).map(UserDMP::getUser)
                .filter(userInfo -> !userInfo.getId().equals(principal.getId())).filter(StreamDistinctBy.distinctByKey(UserInfo::getId))
                .filter(userInfo -> (userInfoRequestItem == null || userInfoRequestItem.getCriteria() == null || userInfoRequestItem.getCriteria().getLike() == null
                        || userInfo.getName().toLowerCase().contains(userInfoRequestItem.getCriteria().getLike().toLowerCase())
                        || (userInfo.getEmail().toLowerCase().contains(userInfoRequestItem.getCriteria().getLike().toLowerCase()))))
                .collect(Collectors.toList());
//                                .where((builder, root) -> builder.like(builder.upper(root.get("name")), "%" + userInfoRequestItem.getCriteria().getLike().toUpperCase() + "%"))

        List<UserInfoInvitationModel> userModels = users.stream().map(userInfo -> new UserInfoInvitationModel().fromDataModel(userInfo)).collect(Collectors.toList());
        return userModels;
    }

    public UUID assignUserAcceptedInvitation(UUID invitationID, Principal principal) throws UnauthorisedException, JAXBException, IOException {
        eu.eudat.data.entities.Invitation invitation = apiContext.getOperationsContext().getDatabaseRepository().getInvitationDao().find(invitationID);
        if (invitation == null)
            throw new UnauthorisedException("There is no Data Management Plan assigned to this Link");
        if (invitation.getAcceptedInvitation()) return invitation.getDmp().getId(); //throw new UnauthorisedException("This Url Has Expired");
        JAXBContext context = JAXBContext.newInstance(Properties.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        Properties properties = (Properties) unmarshaller.unmarshal(new StringReader(invitation.getProperties()));
        UserInfo invitedUser = apiContext.getOperationsContext().getDatabaseRepository().getUserInfoDao().find(principal.getId());
        QueryableList<UserDMP> userDMPQueryableList = apiContext.getOperationsContext().getDatabaseRepository().getUserDmpDao().asQueryable().where(((builder, root) -> builder.and(builder.equal(root.get("dmp").get("id"), invitation.getDmp().getId()), builder.equal(root.get("user").get("id"), invitedUser.getId()))));
        UserDMP existingUserDMP = userDMPQueryableList.getSingleOrDefault();
        if (existingUserDMP != null) {
            if (properties.getRole() != null && existingUserDMP.getRole() > properties.getRole()) {
                existingUserDMP.setRole(properties.getRole());
                DMP datamanagementPlan = invitation.getDmp();
                apiContext.getOperationsContext().getDatabaseRepository().getUserDmpDao().createOrUpdate(existingUserDMP);
                apiContext.getUtilitiesService().getInvitationService().assignToDmp(apiContext.getOperationsContext().getDatabaseRepository().getDmpDao(), existingUserDMP, datamanagementPlan);
                invitation.setAcceptedInvitation(true);
                apiContext.getOperationsContext().getDatabaseRepository().getInvitationDao().createOrUpdate(invitation);
                datamanagementPlan.setUsers(new HashSet<>(apiContext.getOperationsContext().getDatabaseRepository().getUserDmpDao().asQueryable().where((builder, root) -> builder.equal(root.get("dmp").get("id"), datamanagementPlan.getId())).toList()));
                dataManagementPlanManager.updateIndex(datamanagementPlan);
                return datamanagementPlan.getId();
            }
        } else {
            UserDMP userDMP = new UserDMP();
            userDMP.setUser(invitedUser);
            userDMP.setDmp(invitation.getDmp());

            if (properties.getRole() != null) {
                userDMP.setRole(properties.getRole());
            } else {
                userDMP.setRole(UserDMP.UserDMPRoles.USER.getValue());
            }
        /*if (!apiContext.getOperationsContext().getDatabaseRepository().getUserAssociationDao().areAssociated(invitedUser, invitation.getUser())) {
            UserAssociation userAssociation = new UserAssociation();
            userAssociation.setFirstUser(invitedUser);
            userAssociation.setSecondUser(invitation.getUser());
            apiContext.getOperationsContext().getDatabaseRepository().getUserAssociationDao().createOrUpdate(userAssociation);
        }*/
            DMP datamanagementPlan = invitation.getDmp();
            apiContext.getOperationsContext().getDatabaseRepository().getUserDmpDao().createOrUpdate(userDMP);
            apiContext.getUtilitiesService().getInvitationService().assignToDmp(apiContext.getOperationsContext().getDatabaseRepository().getDmpDao(), userDMP, datamanagementPlan);
            invitation.setAcceptedInvitation(true);
            apiContext.getOperationsContext().getDatabaseRepository().getInvitationDao().createOrUpdate(invitation);
            datamanagementPlan.setUsers(new HashSet<>(apiContext.getOperationsContext().getDatabaseRepository().getUserDmpDao().asQueryable().where((builder, root) -> builder.equal(root.get("dmp").get("id"), datamanagementPlan.getId())).toList()));
            dataManagementPlanManager.updateIndex(datamanagementPlan);
            return datamanagementPlan.getId();
        }
        return invitation.getDmp().getId();
    }
}
