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

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.AbstractGeniAggregateManager;
import be.iminds.ilabt.jfed.lowlevel.api.user_spec.UserSpec;
import be.iminds.ilabt.jfed.lowlevel.connection.JFedConnection;
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 java.net.URL;
import java.util.*;

/**
 * AggregateManagerWrapper
 * <p/>
 * Supports only geni_sfa v3 rspec!
 */
public abstract class AggregateManagerWrapper extends ApiWrapper {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(AggregateManagerWrapper.class);

    @Nullable
    protected final Server amServer;
    //a bit of a hack, but the last reply of the AM is sometimes needed by code calling this.
    @Nullable
    protected AbstractGeniAggregateManager.AggregateManagerReply lastAmReply = null;

    protected Map<String, ?> extraOptionsForCallsWithCredential;

    protected List<AnyCredential> speaksForCredentials;

    protected AggregateManagerWrapper(@Nonnull Logger logger, @Nonnull GeniUserProvider geniUserProvider,
                                      @Nonnull JFedConnectionProvider connectionProvider, @Nonnull JFedPreferences jFedPreferences,
                                      @Nullable Server amServer) {
        super(logger, geniUserProvider, connectionProvider, jFedPreferences);

        this.amServer = amServer;
    }

    protected JFedConnection getConnection(@Nonnull ApiInfo.Api api) throws JFedException {
        if (amServer == null)
            throw new IllegalStateException("Cannot create a connection object when specified server is null");

        GeniUser user = geniUserProvider.getLoggedInGeniUser();
        return connectionProvider.getConnectionByAuthority(user, amServer, api);
    }

    protected GeniUser getUser() {
        return geniUserProvider.getLoggedInGeniUser();
    }

    @Nullable
    protected Server getUserAuthority() {
        GeniUser user = geniUserProvider.getLoggedInGeniUser();
        return user.getUserAuthorityServer();
    }

    @Nullable
    public Server getAmServer() {
        return amServer;
    }

    @Override
    public void setSpeaksFor(List<AnyCredential> speaksForCredentials, @Nullable GeniUrn speakingForUrn) {
        super.setSpeaksFor(speaksForCredentials, speakingForUrn);

        //nothing to do here about speaksForUserUrn, override this method to support speaksFor in the AM API specific way!
        //    This is typically done using setExtraOptionsForCallsWithCredential
    }

    public void setExtraOptionsForCallsWithCredential(Map<String, ?> extraOptionsForCallsWithCredential) {
        this.extraOptionsForCallsWithCredential = extraOptionsForCallsWithCredential;
    }

    @Nullable
    protected Map<String, Object> addCredentialExtraOptionsToMap(@Nullable Map<String, Object> extraOptions) {
        if (extraOptionsForCallsWithCredential == null) {
            if (extraOptions == null)
                return null;
            else
                return extraOptions;
        }

        Map<String, Object> res = new HashMap<>();
        if (extraOptions != null) res.putAll(extraOptions);
        res.putAll(extraOptionsForCallsWithCredential);
        return res;
    }

    @Nullable
    protected Map<String, Object> addCredentialExtraOptions(@Nullable Map<String, Object> extraOptions) {
        if (extraOptionsForCallsWithCredential == null)
            return extraOptions;

        Map<String, Object> res = new HashMap<>();
        if (extraOptions != null) res.putAll(extraOptions);
        res.putAll(extraOptionsForCallsWithCredential);
        return res;
    }

    /**
     * @return raw XML-RPC Map result of GetVersion call.
     */
    public abstract Map getVersion() throws JFedException;

    public String listResources(@Nonnull List<AnyCredential> userCredentials, boolean available) throws JFedException {
        if (amServer != null && amServer.getServerType() != null && amServer.getServerType().equalsIgnoreCase("FOAM"))
            return listResources(userCredentials, available, "GENI", "3"); //see FEDIBBTDEV-1392: workaround for bug at al2s (it should ignore case but doesn't)
        else
            return listResources(userCredentials, available, "geni", "3");
    }

    public abstract String listResources(@Nonnull List<AnyCredential> userCredentials, boolean available, @Nonnull String type, @Nonnull String version) throws JFedException;

    /**
     * Creates a sliver by making an allocate and provision request
     */
    public abstract String createSliver(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull String rspec, @Nullable List<UserSpec> userSpecs, @Nullable Date expirationDate)
            throws JFedException;

    public abstract boolean hasSeperateAllocateAndProvision();

    public abstract AllocateReply allocate(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull String rspec, @Nullable Date expirationDate)
            throws JFedException;

    public abstract AllocateReply allocate(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull String rspec, Date startDate, Date endDate)
            throws JFedException;

    public abstract String provisionAndStart(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nullable List<UserSpec> userSpecs, @Nullable Date expirationDate)
            throws JFedException;

    //TODO this should be renamed to "deleteAllSliversInSlice" and a real deleteSliver should be added
    public abstract void deleteSliver(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException;

    @Nullable
    public abstract StatusDetails status(@Nonnull GeniUrn urn/*slice or sliver*/, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException;

    public abstract String describe(@Nonnull GeniUrn urn/*slice or sliver*/, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException;

    /**
     * Renews one or more slivers, or all slivers in a slice.
     * <p>
     * If a slice URN is given, this will assume all slivers in that slice need to be renewed, and will only use the slice URN in the call if possible.
     * If slice URN is null, and sliver URNs is not null and not empty, this call will attempt to renew only these slivers.
     * <p>
     * It is advised to pass all slivers URNs and the slice URN to this call when all slivers in a slice need to be renewed. But only slice URN should work.
     * It is not advised to pass only the sliver URNs if all slivers of the slice need to be renewed (AMv2 cannot do this and not all AMv3 handle this well if geni_single is set).
     *
     * @param sliceUrn         the URN of the slice (may be null in some cases, but best not)
     * @param slivers          the URN of the slivers to be renewed (may be null or empty)
     * @param sliceCredentials creadential for the slice
     * @param newExpire        new expiration time
     */
    public abstract void renewSlivers(@Nullable GeniUrn sliceUrn, @Nullable List<GeniUrn> slivers, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull Date newExpire) throws JFedException;

    @Nullable
    public AbstractGeniAggregateManager.AggregateManagerReply getLastReply() {
        return lastAmReply;
    }

    public abstract boolean isExirationdateSetByCreateSliver();

    //special calls that not every API has. These might choose another implementation than the previous calls!
    public abstract boolean isShareLanSupported();

    public void shareLan(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull String lanClientId, @Nonnull String newSharedLanName) throws JFedException {
        throw new UnsupportedOperationException("AM does not support shareLan");
    }

    public void unshareLan(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull String lanClientId, @Nonnull String sharedLanName) throws JFedException {
        throw new UnsupportedOperationException("AM does not support unshareLan");
    }

    public abstract boolean isRestartSupported();

    public void restart(@Nonnull GeniUrn sliverUrn, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
        throw new UnsupportedOperationException("AM does not support restart");
    }

    public abstract boolean isReloadOSSupported();

    public void reloadOS(@Nonnull GeniUrn sliverUrn, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
        throw new UnsupportedOperationException("AM does not support reloadOS");
    }

    public abstract boolean isOpenConsoleSupported();

    public URL openConsole(@Nonnull GeniUrn sliverUrn, @Nonnull List<AnyCredential> sliceCredentials) throws JFedException {
        throw new UnsupportedOperationException("AM does not support openConsole");
    }

    public abstract boolean isEditSshKeysSupported();

    public void editSshKeys(@Nonnull GeniUrn sliceUrn, @Nonnull List<AnyCredential> sliceCredentials, @Nonnull Collection<UserSpec> userSpecs) throws JFedException {
        throw new UnsupportedOperationException("AM does not support editSshKeys");
    }

    public boolean areUserDiskImageFunctionsSupported() {
        return false;
    }

    public ImageInfo createImage(@Nonnull GeniUrn sliceUrn, List<AnyCredential> sliceCredentials, @Nonnull GeniUrn componentUrn, @Nonnull String imageName,
                                 boolean global, boolean updatePrepare) throws JFedException {
        throw new UnsupportedOperationException("AM does not support createImage");
    }

    @Nonnull
    public List<ImageInfo> listImages(@Nonnull List<AnyCredential> userCredentials) throws JFedException {
        throw new UnsupportedOperationException("AM does not support listImages");
    }

    public boolean deleteImage(@Nonnull List<AnyCredential> userCredentials, @Nonnull GeniUrn imageUrn) throws JFedException {
        throw new UnsupportedOperationException("AM does not support deleteImage");
    }

    public interface AllocateReply {
        String getManifest();

        @Nullable
        List<GeniUrn> getSliverUrns();
    }

    public interface ImageInfo {
        String getUrn();

        String getUrl();
    }

    public static class NotEnoughFreeResourcesException extends JFedException {
        public NotEnoughFreeResourcesException(XMLRPCCallDetails xmlRpcResult, GeniResponseCode geniResponseCode) {
            super("not enough free resources", null, xmlRpcResult, geniResponseCode);
        }

        public NotEnoughFreeResourcesException(String message, XMLRPCCallDetails xmlRpcResult, GeniResponseCode geniResponseCode) {
            super(message, null, xmlRpcResult, geniResponseCode);
        }

        public NotEnoughFreeResourcesException(XmlRpcApiCallReply reply) {
            super("not enough free resources", reply);
        }
    }

    public static class VlanUnavailableException extends JFedException {
        //may be null
        private final Integer vlan;

        public VlanUnavailableException(Integer vlan, XMLRPCCallDetails xmlRpcResult, GeniResponseCode geniResponseCode) {
            super("vlan not available", null, xmlRpcResult, geniResponseCode);
            this.vlan = vlan;
        }

        public VlanUnavailableException(Integer vlan, String message, XMLRPCCallDetails xmlRpcResult, GeniResponseCode geniResponseCode) {
            super(message, null, xmlRpcResult, geniResponseCode);
            this.vlan = vlan;
        }

        public VlanUnavailableException(Integer vlan, XmlRpcApiCallReply reply) {
            super("vlan not available", reply);
            this.vlan = vlan;
        }

        public Integer getVlan() {
            return vlan;
        }
    }

    public static class AnyVlanUnsupportedException extends JFedException {
        public AnyVlanUnsupportedException(XMLRPCCallDetails xmlRpcResult, GeniResponseCode geniResponseCode) {
            super("vlan \"any\" not supported", null, xmlRpcResult, geniResponseCode);
        }

        public AnyVlanUnsupportedException(String message, XMLRPCCallDetails xmlRpcResult, GeniResponseCode geniResponseCode) {
            super(message, null, xmlRpcResult, geniResponseCode);
        }

        public AnyVlanUnsupportedException(XmlRpcApiCallReply reply) {
            super("vlan \"any\" not supported", reply);
        }
    }

    public static class NoAmException extends RuntimeException {
        public NoAmException() {
        }

        public NoAmException(String message) {
            super(message);
        }

        public NoAmException(String message, Throwable cause) {
            super(message, cause);
        }

        public NoAmException(Throwable cause) {
            super(cause);
        }

        public NoAmException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
}
