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

import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.*;
import be.iminds.ilabt.jfed.lowlevel.connection.SfaConnection;
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.util.*;

/**
 * AbstractGeniClearingHouseApi
 */
public abstract class AbstractFederationApi2 extends AbstractFederationApi {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(AbstractFederationApi2.class);

    public AbstractFederationApi2(Logger logger, RetrySettings retrySettings, ApiInfo.Api serverType, JFedPreferences jFedPreferences) {
        super(logger, retrySettings, serverType, jFedPreferences);
    }

//    /**
//     * Convenience method for genericLookup without lookupResultConverter
//     */
//    @Nonnull
//    protected FederationApiReply<LookupResult<?>> genericLookup(@Nullable Map<String, Object> methodParams,
//                                                                 @Nonnull SfaConnection con,
//                                                                 @Nonnull String methodJavaName,
//                                                                 @Nonnull String objectTypeName,
//                                                                 @Nullable List<AnyCredential> credentialList,
//                                                                 @Nullable Map<String, ?> match,
//                                                                 @Nullable List<String> filter,
//                                                                 @Nullable Map<String, Object> extraOptions,
//                                                                 @Nullable List<Object> extraArguments /* extraArguments are added in front of call*/) throws JFedException {
//
//        return internalLookup(methodParams, con, methodJavaName, objectTypeName, credentialList, match, filter, extraOptions, extraArguments,
//                HTLookupResult::new);
//    }

    @Nonnull
    protected <T> FederationApiReply<LookupResult<T>> genericLookup(@Nullable Map<String, Object> methodParams,
                                                                    @Nonnull SfaConnection con,
                                                                    @Nonnull String methodJavaName,
                                                                    @Nonnull String objectTypeName,
                                                                    @Nullable List<AnyCredential> credentialList,
                                                                    @Nullable Map<String, ?> match,
                                                                    @Nullable List<String> filter,
                                                                    @Nullable Map<String, Object> extraOptions,
                                                                    @Nullable List<Object> extraArguments,
                                                                    @Nonnull LookupResultConverter<T> lookupResultConverter) throws JFedException {
        return internalLookup(methodParams, con, methodJavaName, objectTypeName, credentialList, match, filter, extraOptions, extraArguments,
                resultValueObject -> new LookupResult<>(resultValueObject, lookupResultConverter));
    }

    @Nonnull
    protected <R> FederationApiReply<R> internalLookup(@Nullable Map<String, Object> methodParams,
                                                       @Nonnull SfaConnection con,
                                                       @Nonnull String methodJavaName,
                                                       @Nonnull String objectTypeName,
                                                       @Nullable List<AnyCredential> credentialList,
                                                       @Nullable Map<String, ?> match,
                                                       @Nullable List<String> filter,
                                                       @Nullable Map<String, Object> extraOptions,
                                                       @Nullable List<Object> extraArguments /* extraArguments are added in front of call*/,
                                                       @Nonnull ReplyConverter<R> converter) throws JFedException {
        if (methodParams != null) methodParams.put("type", objectTypeName);

        List<Object> args = new ArrayList<>(4);
        if (extraArguments != null)
            extraArguments.forEach(args::add);

        args.add(objectTypeName);

        if (credentialList != null) {
            args.add(createCredentialsListWithTypeAndVersion(credentialList));
        } else {
            //always need to add credential argument
            args.add(Collections.emptyList());
        }

        Map<String, Object> options = createOptionsMap(extraOptions);
        if (match != null)
            options.put("match", match);
        if (filter != null)
            options.put("filter", filter);
        args.add(options);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, methodJavaName, "lookup", args,
                resultValueObject -> {
                    if (resultValueObject == null || Objects.equals(resultValueObject, 0))
                        return null;
                    else {
                        return converter.convertXMLRPCCall(resultValueObject);
                    }
                });
    }


    /**
     * normally returns nothing at all. Anything that might be returned anyway is converted to string, but normally a null Object will be returned.
     */
    @Nonnull
    protected FederationApiReply<String> update_internal(Map<String, Object> methodParams,
                                                         @Nonnull SfaConnection con,
                                                         @Nonnull String methodJavaName,
                                                         @Nonnull String objectTypeName,
                                                         @Nonnull List<AnyCredential> credentialList,
                                                         @Nonnull String urn,
                                                         @Nullable String urnType,
                                                         @Nonnull Map<String, String> fields,
                                                         @Nullable Map<String, Object> extraOptions) throws JFedException {
        return update_internal(methodParams, con, methodJavaName, objectTypeName, credentialList, urn, urnType, fields, extraOptions, null);
    }

    @Nonnull
    protected FederationApiReply<String> update_internal(Map<String, Object> methodParams,
                                                         @Nonnull SfaConnection con,
                                                         @Nonnull String methodJavaName,
                                                         @Nonnull String objectTypeName,
                                                         @Nonnull List<AnyCredential> credentialList,
                                                         @Nonnull String urn,
                                                         @Nullable String urnType,
                                                         @Nonnull Map<String, String> fields,
                                                         @Nullable Map<String, Object> extraOptions,
                                                         @Nullable List<Object> extraArguments /* extraArguments are added in front of call*/) throws JFedException {
        if (methodParams != null) methodParams.put("type", objectTypeName);

        assert !fields.isEmpty();

        GeniUrn geniUrn = GeniUrn.parse(urn);
        if (geniUrn == null || (urnType != null && !Objects.equals(geniUrn.getEncodedResourceType(), urnType)))
            LOG.error("WARNING: URN argument to " + methodJavaName + " is not a valid " + (urnType == null ? ""
                    : urnType) + " urn: \"" + urn + "\" (will be used anyway)");

        List<Object> args = new ArrayList<>(4);
        if (extraArguments != null)
            extraArguments.forEach(args::add);

        args.add(objectTypeName);

        args.add(urn);

        args.add(createCredentialsListWithTypeAndVersion(credentialList));

        Map<String, Object> options = new HashMap<>();
        options.put("fields", fields);
        if (extraOptions != null)
            options.putAll(extraOptions);
        args.add(options);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, methodJavaName, "update", args,
                STRING_REPLY_CONVERTER);
    }

    @Nonnull
    protected <R> FederationApiReply<R> create_internal(@Nullable Map<String, Object> methodParams,
                                                        @Nonnull SfaConnection con,
                                                        @Nonnull String methodJavaName,
                                                        @Nonnull String objectTypeName,
                                                        @Nonnull List<AnyCredential> credentialList,
                                                        @Nonnull Map<String, String> fields,
                                                        @Nullable Map<String, Object> extraOptions,
                                                        @Nonnull ReplyConverter<R> replyConverter) throws JFedException {
        if (methodParams != null) methodParams.put("type", objectTypeName);
        assert !fields.isEmpty();

        List<Object> args = new ArrayList<>(3);

        args.add(objectTypeName);
        args.add(createCredentialsListWithTypeAndVersion(credentialList));

        Map<String, Object> options = createOptionsMap(extraOptions);
        options.put("fields", fields);

        args.add(options);

        return executeAndLogXmlRpcCommandGeni(methodParams, con, methodJavaName, "create", args, replyConverter);
    }

    @Nonnull
    public FederationApiReply<Boolean> delete_internal(@Nullable Map<String, Object> methodParams,
                                                       @Nonnull SfaConnection con,
                                                       @Nonnull String methodJavaName,
                                                       @Nonnull String objectTypeName,
                                                       @Nonnull List<AnyCredential> credentialList,
                                                       @Nonnull String urnToDelete,
                                                       @Nullable Map<String, Object> extraOptions) throws JFedException {
        if (methodParams != null) methodParams.put("type", objectTypeName);

        List<Object> args = new ArrayList<>(4);

        args.add(objectTypeName);
        args.add(urnToDelete);
        args.add(createCredentialsListWithTypeAndVersion(credentialList));
        args.add(createOptionsMap(extraOptions));

        return executeAndLogXmlRpcCommandGeni(methodParams, con, methodJavaName, "delete", args,
                BOOLEAN_REPLY_CONVERTER, false);
    }


    @Nonnull
    public FederationApiReply<List<AnyCredential>> getCredentials_internal(@Nullable Map<String, Object> methodParams,
                                                                           @Nonnull SfaConnection con,
                                                                           @Nonnull String methodJavaName,
                                                                           @Nonnull String objectTypeName,
                                                                           @Nonnull List<AnyCredential> credentialList,
                                                                           @Nonnull GeniUrn urn,
                                                                           @Nullable Map<String, Object> extraOptions) throws JFedException {
        if (methodParams != null) methodParams.put("type", objectTypeName);

        List<Object> args = new ArrayList<>(3);
        args.add(urn.toString());

        args.add(createCredentialsListWithTypeAndVersion(credentialList));
        args.add(createOptionsMap(extraOptions));

        return executeAndLogXmlRpcCommandGeni(methodParams, con, methodJavaName, "get_credentials", args, resultValueObject ->
                apiSpecifiesListOfCredentials("get_credentials" + " " + objectTypeName, resultValueObject)
        );
    }
}
