package {{invokerPackage}};

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.*;

import retrofit2.Retrofit;
import retrofit2.converter.scalars.ScalarsConverterFactory;
import retrofit2.converter.jackson.JacksonConverterFactory;

import play.libs.Json;
import play.libs.ws.WSClient;

import {{invokerPackage}}.Play24CallAdapterFactory;
import {{invokerPackage}}.Play24CallFactory;

import okhttp3.Interceptor;
import {{invokerPackage}}.auth.ApiKeyAuth;
import {{invokerPackage}}.auth.Authentication;

/**
 * API client
 */
public class ApiClient {

    /** Underlying HTTP-client */
    private WSClient wsClient;

    /** Supported auths */
    private Map<String, Authentication> authentications;

    /** API base path */
    private String basePath = "{{{basePath}}}";

    public ApiClient(WSClient wsClient) {
        this();
        this.wsClient = wsClient;
    }

    public ApiClient() {
        // Setup authentications (key: authentication name, value: authentication).
        authentications = new HashMap<>();{{#authMethods}}{{#isBasic}}
        // authentications.put("{{name}}", new HttpBasicAuth());{{/isBasic}}{{#isApiKey}}
        authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{^isKeyInHeader}}"query"{{/isKeyInHeader}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}}
        // authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}}
        // Prevent the authentications from being modified.
        authentications = Collections.unmodifiableMap(authentications);

    }

    /**
     * Creates a retrofit2 client for given API interface
     */
    public <S> S createService(Class<S> serviceClass) {
        if(!basePath.endsWith("/")) {
            basePath = basePath + "/";
        }

        Map<String, String> extraHeaders = new HashMap<>();
        List<Pair> extraQueryParams = new ArrayList<>();

        for (String authName : authentications.keySet()) {
            Authentication auth = authentications.get(authName);
            if (auth == null) throw new RuntimeException("Authentication undefined: " + authName);

            auth.applyToParams(extraQueryParams, extraHeaders);
        }

        return new Retrofit.Builder()
                       .baseUrl(basePath)
                       .addConverterFactory(ScalarsConverterFactory.create())
                       .addConverterFactory(JacksonConverterFactory.create(Json.mapper()))
                       .callFactory(new Play24CallFactory(wsClient, extraHeaders, extraQueryParams))
                       .addCallAdapterFactory(new Play24CallAdapterFactory())
                       .build()
                       .create(serviceClass);
    }

    /**
     * Helper method to set API base path
     */
    public ApiClient setBasePath(String basePath) {
        this.basePath = basePath;
        return this;
    }

    /**
     * Get authentications (key: authentication name, value: authentication).
     */
    public Map<String, Authentication> getAuthentications() {
        return authentications;
    }

    /**
     * Get authentication for the given name.
     *
     * @param authName The authentication name
     * @return The authentication, null if not found
     */
    public Authentication getAuthentication(String authName) {
        return authentications.get(authName);
    }

    /**
     * Helper method to set API key value for the first API key authentication.
     */
    public ApiClient setApiKey(String apiKey) {
        for (Authentication auth : authentications.values()) {
            if (auth instanceof ApiKeyAuth) {
                ((ApiKeyAuth) auth).setApiKey(apiKey);
                return this;
            }
        }

        throw new RuntimeException("No API key authentication configured!");
    }

    /**
     * Helper method to set API key prefix for the first API key authentication.
     */
    public ApiClient setApiKeyPrefix(String apiKeyPrefix) {
        for (Authentication auth : authentications.values()) {
            if (auth instanceof ApiKeyAuth) {
                ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix);
                return this;
            }
        }

        throw new RuntimeException("No API key authentication configured!");
    }


}


