package be.iminds.ilabt.jfed.lowlevel.api_wrapper.impl;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.*;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.UserAndSliceApiWrapper;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnectionProvider;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.util.GeniUrn;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.util.*;

/**
 * AutomaticUserAndSliceApiWrapper automatically chooses the best UserAndSliceApiWrapper implementation
 * for the situation and uses it
 * <p/>
 * NOTE: AutomaticUserAndSliceApiWrapper has a lock built-in, meaning it may be called asynchronously
 * -> but that CAN give deadlocks etc.
 */
public class AutomaticUserAndSliceApiWrapper extends UserAndSliceApiWrapper {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(AutomaticUserAndSliceApiWrapper.class);

    protected ApiInfo.Api api;
    protected UserAndSliceApiWrapper impl;

    private AutomaticUserAndSliceApiWrapper(Logger logger, GeniUserProvider geniUserProvider, JFedConnectionProvider connectionProvider, JFedPreferences jFedPreferences) {
        super(logger, geniUserProvider, connectionProvider, jFedPreferences);

        choose();
    }

    private GeniUser userChoosenFor;

    protected void reChooseIfNeeded() {
        assert geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer() != null;
        if (userChoosenFor != geniUserProvider.getLoggedInGeniUser()
                &&
                (userChoosenFor == null || userChoosenFor.getUserAuthorityServer() == null
                        || !Objects.equals(userChoosenFor.getUserAuthorityServer().getId(),
                        geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer().getId()))) {
            LOG.info("New login detected: will re-choose authority servers");
            choose();
        }
    }

    protected void choose() {
        if (!geniUserProvider.isUserLoggedIn()) {
            LOG.warn("Cannot choose implementation when user is not logged in");
            api = null;
            impl = null;
            userChoosenFor = null;
            return;
        }
        userChoosenFor = geniUserProvider.getLoggedInGeniUser();

        Server userServer = getLoggedInUserAuthorityServer();
        if (userServer == null) {
            LOG.warn("Cannot choose implementation when userServer is unknown");
            api = null;
            impl = null;
            return;
        }

        if (userServer.getServices() == null)
            throw new RuntimeException("Cannot initialize AutomaticUserAndSliceApiWrapper: userAuth.getServices() = null (userAuth.urn=" + userServer.getDefaultComponentManagerUrn() + ")");

        if (ApiInfo.hasService(userServer, ApiInfo.ApiName.GENI_CH_SA, 2) &&
                ApiInfo.hasService(userServer, ApiInfo.ApiName.GENI_CH_MA, 2)) {
            api = null;
            impl =
                    new UniformFederationApi2UserAndSliceApiWrapper(logger, geniUserProvider, connectionProvider, jFedPreferences);

        } else if (ApiInfo.hasService(userServer, ApiInfo.ApiName.PROTOGENI_SA, 1)) {
            api = new ApiInfo.Api(ApiInfo.ApiName.PROTOGENI_SA, 1);
            impl = new ProtoGeniSAUserAndSliceApiWrapper(logger, geniUserProvider, connectionProvider, jFedPreferences);
        } else if (ApiInfo.hasService(userServer, ApiInfo.ApiName.GENI_CH_SA, 1) &&
                ApiInfo.hasService(userServer, ApiInfo.ApiName.GENI_CH_MA, 1)) {
            api = null;
            impl =
                    new UniformFederationApi1UserAndSliceApiWrapper(logger, geniUserProvider, connectionProvider, jFedPreferences);

        } else if (ApiInfo.hasService(userServer, ApiInfo.ApiName.PLANETLAB_SLICE_REGISTRY, 1)) {
            impl =
                    new PlanetlabRegistryUserAndSliceApiWrapper(logger, geniUserProvider, connectionProvider, jFedPreferences);
        } else
            throw new RuntimeException("There is no server known at the users authority that can be used to" +
                    " retrieve user and slice credentials and create slices. user authority: " + userServer.getDefaultComponentManagerUrn());
    }

    protected UserAndSliceApiWrapper getImpl() {
        reChooseIfNeeded();
        if (impl == null)
            choose();
        if (impl == null) {
            LOG.warn("No user logged in, so using DO NOTHING implementation of UserAndSliceApiWrapper");
            return new UserAndSliceApiWrapper(logger, geniUserProvider, connectionProvider, jFedPreferences) {
                @Override
                public boolean hasUserCredentials() {
                    return true;
                }

                @Override
                public boolean hasSpeaksForSupport() {
                    return false;
                }

                @Override
                public List<AnyCredential> getCachedUserCredentialsForAM() {
                    return Collections.emptyList();
                }

                @Override
                public List<AnyCredential> getUserCredentials(@Nonnull Logger logger, @Nonnull GeniUrn userUrn) throws JFedException {
                    return Collections.emptyList();
                }

                @Override
                public List<AnyCredential> getSliceCredentials(@Nonnull Logger logger, @Nonnull GeniUrn sliceUrn) throws JFedException {
                    return Collections.emptyList();
                }

                @Override
                public List<GeniUrn> getSlicesForUser(@Nonnull Logger logger, @Nonnull GeniUrn userUrn) throws JFedException {
                    return Collections.emptyList();
                }

                @Override
                public List<GeniUrn> getAggregatesForSlice(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn sliceUrn) throws JFedException {
                    return Collections.emptyList();
                }

                @Override
                public Date getSliceExpiration(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn sliceUrn) throws JFedException {
                    return new Date();
                }

                @Override
                public boolean isRegisterAggregatesForSliceSupported() {
                    return false;
                }

                @Override
                public void registerAggregatesForSlice(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn sliceUrn, @Nonnull GeniUrn aggregateUrn, @Nonnull Collection<GeniUrn> sliverUrns, @Nonnull Date sliverExpirationDate) throws JFedException {

                }

                @Override
                public void updateSliverExpirationDateInfoForSlice(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn sliceUrn, @Nonnull Collection<GeniUrn> sliverUrns, @Nonnull Date sliverExpirationDate) throws JFedException {

                }

                @Override
                public void unregisterAggregatesForSlice(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn sliceUrn, @Nonnull GeniUrn aggregateUrn, @Nullable Collection<GeniUrn> sliverUrns) throws JFedException {

                }

                @Override
                public SliceInfo createSlice(@Nonnull Logger logger, @Nonnull String sliceName, Date expirationDate, String subAuthName) throws JFedException {
                    return null;
                }

                @Override
                public List<AnyCredential> renewSlice(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull Date newExpirationDate) throws JFedException {
                    return Collections.emptyList();
                }

                @Override
                public List<String> getSshKeysForUser(@Nonnull Logger logger, @Nonnull GeniUrn userUrn) throws JFedException {
                    return Collections.emptyList();
                }

                @Override
                public SubAuthoritySupport getSubAuthoritySupport(@Nonnull Logger logger) throws JFedException {
                    return SubAuthoritySupport.SUB_AUTHORITY_OPTIONAL;
                }

                @Override
                public List<String> getSubAuthorityNames(@Nonnull Logger logger, @Nonnull GeniUrn user) throws JFedException {
                    return Collections.emptyList();
                }

                @Override
                public void shareSlice(@Nonnull Logger logger, @Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn userUrn) throws JFedException {

                }

                @Override
                public List<GeniUrn> getUsersForSubAuthority(@Nonnull Logger logger, @Nonnull String projectName) throws JFedException {
                    return null;
                }

                @Override
                public List<GeniUrn> getUsersForSlice(@Nonnull Logger logger, @Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
                    return null;
                }

                @Override
                public void addUsersToSlice(@Nonnull Logger logger, @Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull List<GeniUrn> userUrns, @Nonnull String role) throws JFedException {

                }
            };
        }
        return impl;
    }

    //only overridden for ApiMethod annotation...
    @SuppressWarnings("EmptyMethod")
    @ApiMethod(order = 0, hint = "getLoggedinUserCredentials")
    @Override
    public List<AnyCredential> getLocalUserCredentials(@Nonnull Logger logger)
            throws JFedException {
        return super.getLocalUserCredentials(logger);
    }

    @ApiMethod(order = 1, hint = "getUserCredentials")
    @Override
    public List<AnyCredential> getUserCredentials(
            @Nonnull Logger logger,
            @Nonnull @ApiMethodParameter(name = "userUrn", hint = "The urn of the user to request credentials for", parameterType = ApiMethodParameterType.USER_URN)
                    GeniUrn userUrn)
            throws JFedException {

        return getImpl().getUserCredentials(logger, userUrn);
    }

    private static final long CREDENTIAL_MINIMUM_LIFE_MINUTES = 5;

    private static Date getEarliestExpireDate(List<AnyCredential> credentials) {
        if (credentials == null || credentials.isEmpty())
            return null;

        Date res = null;

        for (AnyCredential cred : credentials) {
            Date expireDate = cred.getExpiresDate();

            //ignore if no date found (= assume valid)
            if (expireDate == null)
                continue;

            if (res == null || expireDate.before(res))
                res = expireDate;
        }

        return res;

    }

    private static boolean hasUsableCredentials(List<AnyCredential> credentials) {
        if (credentials == null || credentials.isEmpty())
            return false;

        Date now = new Date();
        Date minimum = new Date(now.getTime() + (CREDENTIAL_MINIMUM_LIFE_MINUTES * 60 * 1000L));

        Date expireDate = getEarliestExpireDate(credentials);

        //if no expire date found, assume valid long enough!
        if (expireDate == null) {
            LOG.warn("No expire date found in credentials. Assuming valid long enough.");
            return true;
        }

        return expireDate.after(minimum);
    }

    public List<AnyCredential> getUserCredentialsWithRetry(Logger logger, GeniUrn userUrn) throws InterruptedException, JFedException {
        List<AnyCredential> res = null;

        boolean tryGetCredentials = true;
        int attempts = 0;
        while (tryGetCredentials) {
            attempts++;
            res = getImpl().getUserCredentials(logger, userUrn);

            if (!hasUsableCredentials(res)) {
                if (attempts >= 5) {
                    LOG.warn("Too many attempts (" + attempts + ") to get usable credentials, will give up without usable credentials.");
                    tryGetCredentials = false;
                } else {
                    Date expireDate = getEarliestExpireDate(res);
                    long expireSeconds =
                            expireDate == null ? -1 : ((expireDate.getTime() - System.currentTimeMillis()) / 1000L);

                    if (attempts == 2 && expireDate != null) {
                        //on second attempt, wait until expires
                        long waitSeconds =
                                expireSeconds > 0 ?
                                        expireSeconds + 2 :
                                        1;

                        LOG.warn("Got NON usable credentials (expires in " + expireSeconds + " s), " +
                                "will wait until after credential expires (= " + waitSeconds + " s) and try again.");
                        tryGetCredentials = true;
                        Thread.sleep(waitSeconds * 1000L);
                    } else {
                        LOG.warn("Got NON usable credentials (expires in " + expireSeconds + " s), " +
                                "will wait 5 seconds and try again.");
                        tryGetCredentials = true;
                        Thread.sleep(5 * 1000L);
                    }
                }
            } else {
                LOG.debug("Got usable credentials");
                tryGetCredentials = false;
            }
        }

        //return, even if not usable
        return res;
    }

    @Override
    public boolean hasUserCredentials() {
        return getImpl().hasUserCredentials();
    }

    @Override
    public boolean hasSpeaksForSupport() {
        return getImpl().hasSpeaksForSupport();
    }

    @Override
    public void setSpeaksFor(List<AnyCredential> speaksForCredentials, @Nullable GeniUrn speakingForUrn) {
        super.setSpeaksFor(speaksForCredentials, speakingForUrn);
        //also delegate this!
        getImpl().setSpeaksFor(speaksForCredentials, speakingForUrn);
    }

    @Override
    public List<AnyCredential> getCachedUserCredentialsForAM() {
        return getImpl().getCachedUserCredentialsForAM();
    }

    @ApiMethod(order = 2, hint = "getSliceCredentials")
    @Override
    public List<AnyCredential> getSliceCredentials(
            @Nonnull Logger logger,
            @Nonnull @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
                    GeniUrn sliceUrn) throws JFedException {
        return getImpl().getSliceCredentials(logger, sliceUrn);
    }

    @ApiMethod(order = 3, hint = "getSlicesForUser")
    @Override
    public List<GeniUrn> getSlicesForUser(
            @Nonnull Logger logger,
            @Nonnull @ApiMethodParameter(name = "userUrn", parameterType = ApiMethodParameterType.USER_URN)
                    GeniUrn userUrn) throws JFedException {
        return getImpl().getSlicesForUser(logger, userUrn);
    }

    @ApiMethod(order = 4, hint = "getAggregatesForSlice")
    @Override
    public List<GeniUrn> getAggregatesForSlice(
            @Nonnull Logger logger,
            @Nonnull @ApiMethodParameter(name = "sliceCredentials", hint = "slice credentials (only used if needed to get slice aggregate info)", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                    List<AnyCredential> sliceCredentials,
            @Nonnull @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
                    GeniUrn sliceUrn) throws JFedException {
        return getImpl().getAggregatesForSlice(logger, sliceCredentials, sliceUrn);
    }

    @ApiMethod(order = 4, hint = "getSliceExpiration")
    @Override
    public Date getSliceExpiration(
            @Nonnull Logger logger,
            @Nonnull @ApiMethodParameter(name = "sliceCredentials", hint = "slice credentials (only used if needed to get slice expiration info)", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                    List<AnyCredential> sliceCredentials,
            @Nonnull @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
                    GeniUrn sliceUrn) throws JFedException {
        Date res = getImpl().getSliceExpiration(logger, sliceCredentials, sliceUrn);
        if (res != null)
            return res;
        //fall back to credential info
        for (AnyCredential c : sliceCredentials) {
            if (!c.isSpeaksFor() && c.getExpiresDate() != null) {
                //prefer non ABAC, but use ABAC if it's the only one
                if (res == null || !(c instanceof AbacCredential))
                    res = c.getExpiresDate();
            }
        }
        return res;
    }

    @Override
    public boolean isRegisterAggregatesForSliceSupported() {
        return getImpl().isRegisterAggregatesForSliceSupported();
    }

    @Override
    public void registerAggregatesForSlice(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn sliceUrn, @Nonnull GeniUrn aggregateUrn, @Nonnull Collection<GeniUrn> sliverUrns, @Nonnull Date sliverExpirationDate) throws JFedException {
        try {
            getImpl().registerAggregatesForSlice(logger, sliceCredentials, sliceUrn, aggregateUrn, sliverUrns, sliverExpirationDate);
        } catch (UnsupportedOperationException e) {
            LOG.debug("API does not support registerAggregatesForSlice. This can be ignored.", e);
        }
    }

    @Override
    public void updateSliverExpirationDateInfoForSlice(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn sliceUrn, @Nonnull Collection<GeniUrn> sliverUrns, @Nonnull Date sliverExpirationDate) throws JFedException {
        try {
            getImpl().updateSliverExpirationDateInfoForSlice(logger, sliceCredentials, sliceUrn, sliverUrns, sliverExpirationDate);
        } catch (UnsupportedOperationException e) {
            LOG.debug("API does not support updateSliverExpirationDateInfoForSlice. This can be ignored.", e);
        }
    }

    @Override
    public void unregisterAggregatesForSlice(@Nonnull Logger logger, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn sliceUrn, @Nonnull GeniUrn aggregateUrn, @Nullable Collection<GeniUrn> sliverUrns) throws JFedException {
        try {
            getImpl().unregisterAggregatesForSlice(logger, sliceCredentials, sliceUrn, aggregateUrn, sliverUrns);
        } catch (UnsupportedOperationException e) {
            LOG.debug("API does not support unregisterAggregatesForSlice. This can be ignored.", e);
        }
    }

    @ApiMethod(order = 5, hint = "createSlice")
    @Override
    public SliceInfo createSlice(
            @Nonnull Logger logger,
            @Nonnull @ApiMethodParameter(name = "sliceName", parameterType = ApiMethodParameterType.STRING)
                    String sliceName,
            @ApiMethodParameter(name = "expirationDate", parameterType = ApiMethodParameterType.DATE, guiDefaultOptional = true, required = false)
                    Date expirationDate,
            @ApiMethodParameter(name = "subAuthName", parameterType = ApiMethodParameterType.STRING, required = false)
                    String subAuthName) throws JFedException {
        return getImpl().createSlice(logger, sliceName, expirationDate, subAuthName);
    }

    @ApiMethod(order = 6, hint = "renewSlice")
    @Override
    public List<AnyCredential> renewSlice(
            @Nonnull Logger logger,
            @Nonnull @ApiMethodParameter(name = "sliceCredentials", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                    List<AnyCredential> sliceCredentials,
            @Nonnull @ApiMethodParameter(name = "newExpirationDate", parameterType = ApiMethodParameterType.DATE)
                    Date newExpirationDate) throws JFedException {
        return getImpl().renewSlice(logger, sliceCredentials, newExpirationDate);
    }

    @ApiMethod(order = 7, hint = "getSshKeysForUser")
    @Override
    public List<String> getSshKeysForUser(
            @Nonnull Logger logger,
            @Nonnull @ApiMethodParameter(name = "userUrn", parameterType = ApiMethodParameterType.USER_URN)
                    GeniUrn userUrn) throws JFedException {
        return getImpl().getSshKeysForUser(logger, userUrn);
    }

    //    @ApiMethod(order=8, hint="getSubAuthoritySupport")
    @Override
    public SubAuthoritySupport getSubAuthoritySupport(
            @Nonnull Logger logger) throws JFedException {
        return getImpl().getSubAuthoritySupport(logger);
    }

    @ApiMethod(order = 9, hint = "getSubAuthorityNames")
    @Override
    public List<String> getSubAuthorityNames(
            @Nonnull Logger logger,
            @Nonnull @ApiMethodParameter(name = "user", parameterType = ApiMethodParameterType.USER_URN)
                    GeniUrn user) throws JFedException {
        return getImpl().getSubAuthorityNames(logger, user);
    }

    @Override
    public void shareSlice(@Nonnull Logger logger, @Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull GeniUrn userUrn) throws JFedException {
        getImpl().shareSlice(logger, sliceUrn, sliceCredentials, userUrn);
    }

    @Override
    public List<GeniUrn> getUsersForSubAuthority(@Nonnull Logger logger, @Nonnull String projectName) throws JFedException {
        return getImpl().getUsersForSubAuthority(logger, projectName);
    }

    @Override
    public List<GeniUrn> getUsersForSlice(@Nonnull Logger logger, @Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
        return getImpl().getUsersForSlice(logger, sliceUrn, sliceCredentials);
    }

    @Override
    public void addUsersToSlice(@Nonnull Logger logger, @Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull List<GeniUrn> userUrns, @Nonnull String role) throws JFedException {
        getImpl().addUsersToSlice(logger, sliceUrn, sliceCredentials, userUrns, role);
    }

    @Override
    public void setExtraOptionsForCallsWithCredential(Map<String, ?> extraOptionsForCallsWithCredential) {
        super.setExtraOptionsForCallsWithCredential(extraOptionsForCallsWithCredential);
        getImpl().setExtraOptionsForCallsWithCredential(extraOptionsForCallsWithCredential);
    }

    public interface AutomaticUserAndSliceApiWrapperFactory {
        AutomaticUserAndSliceApiWrapper create();

        AutomaticUserAndSliceApiWrapper create(Logger altLogger);

        AutomaticUserAndSliceApiWrapper create(GeniUser altUser);

        AutomaticUserAndSliceApiWrapper create(Logger altLogger, GeniUser altUser);
    }

    public static class BasicAutomaticUserAndSliceApiWrapperFactory implements AutomaticUserAndSliceApiWrapperFactory {
        private final Logger logger;
        private final GeniUserProvider geniUserProvider;
        private final JFedConnectionProvider connectionProvider;
        private final JFedPreferences jFedPreferences;

        @Inject
        public BasicAutomaticUserAndSliceApiWrapperFactory(Logger logger, GeniUserProvider geniUserProvider, JFedConnectionProvider connectionProvider, JFedPreferences jFedPreferences) {
            this.logger = logger;
            this.geniUserProvider = geniUserProvider;
            this.connectionProvider = connectionProvider;
            this.jFedPreferences = jFedPreferences;
        }

        public BasicAutomaticUserAndSliceApiWrapperFactory(Logger logger, final GeniUser geniUser, JFedConnectionProvider connectionProvider, JFedPreferences jFedPreferences) {
            this(logger, new GeniUserProvider() {
                public GeniUser getLoggedInGeniUser() {
                    return geniUser;
                }

                public boolean isUserLoggedIn() {
                    return geniUser != null;
                }
            }, connectionProvider, jFedPreferences);
        }

        public AutomaticUserAndSliceApiWrapper create() {
            return this.create(logger);
        }

        public AutomaticUserAndSliceApiWrapper create(Logger altLogger) {
            return create(altLogger, geniUserProvider);
        }

        public AutomaticUserAndSliceApiWrapper create(Logger altLogger, final GeniUser altUser) {
            return create(altLogger, new GeniUserProvider() {
                @Override
                public GeniUser getLoggedInGeniUser() {
                    return altUser;
                }

                @Override
                public boolean isUserLoggedIn() {
                    return altUser != null;
                }
            });
        }

        public AutomaticUserAndSliceApiWrapper create(Logger altLogger, GeniUserProvider usedGeniUserProvider) {
            AutomaticUserAndSliceApiWrapper res =
                    new AutomaticUserAndSliceApiWrapper(altLogger, usedGeniUserProvider, connectionProvider, jFedPreferences);
            if (geniUserProvider.isUserLoggedIn() && geniUserProvider.getLoggedInGeniUser() instanceof GeniUserWithSpeaksFor) {
                GeniUserWithSpeaksFor geniUserWithSpeaksFor =
                        (GeniUserWithSpeaksFor) geniUserProvider.getLoggedInGeniUser();
                res.setSpeaksFor(geniUserWithSpeaksFor.getSpeaksForCredentials(), geniUserWithSpeaksFor.getSpeaksForUrn());
            }
            return res;
        }

        public AutomaticUserAndSliceApiWrapper create(final GeniUser altUser) {
            return this.create(logger, altUser);
        }
    }
}
