package com.ydl.ydlnet.builder.factory;

import androidx.annotation.Nullable;
import com.ydl.ydlnet.builder.manage.HttpUrlManager;
import com.ydl.ydlnet.builder.retrofit.RetrofitBuilder;
import com.ydl.ydlnet.cache.utils.LruCache;
import io.reactivex.Observable;
import io.reactivex.Single;
import okhttp3.OkHttpClient;
import retrofit2.CallAdapter;
import retrofit2.Converter;
import retrofit2.Retrofit;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;

/**
 * Created by haorui on 2019-09-02 .
 * Des: API工厂类
 */
public class ApiFactory {

    private volatile static ApiFactory instance;

    /**
     * 缓存retrofit Api Service对象
     */
    private static LruCache<String, Object> retrofitServiceCache;

    private CallAdapter.Factory[] callAdapterFactory;

    private Converter.Factory[] converterFactory;

    private OkHttpClient okHttpClient;
    private Retrofit retrofit;

    public static ApiFactory getInstance() {
        if (instance == null) {
            synchronized (ApiFactory.class) {
                if (instance == null) {
                    instance = new ApiFactory();
                }
            }
        }
        return instance;
    }

    private ApiFactory() {
        retrofitServiceCache = new LruCache<>(15);
    }

    /**
     * 清空所有api缓存（用于切换环境时候使用）
     */
    public void clearAllApi() {
        retrofitServiceCache.clear();
    }

    public ApiFactory setCallAdapterFactory(CallAdapter.Factory... callAdapterFactory) {
        this.callAdapterFactory = callAdapterFactory;
        return this;
    }

    public ApiFactory setConverterFactory(Converter.Factory... converterFactory) {
        this.converterFactory = converterFactory;
        return this;
    }

    public ApiFactory setOkClient(OkHttpClient okHttpClient) {
        this.okHttpClient = okHttpClient;
        return this;
    }

    public ApiFactory setBaseUrl(String baseUrl) {
        HttpUrlManager.Companion.getInstance().setUrl(baseUrl);
        return this;
    }

    public ApiFactory addUrl(String domainName, String url) {
        HttpUrlManager.Companion.getInstance().addUrl(domainName,url);
        return this;
    }

    public ApiFactory setMultipleUrlMap(HashMap<String, String> multipleUrlMap) {
        HttpUrlManager.Companion.getInstance().setMultipleUrl(multipleUrlMap);
        return this;
    }

    public boolean isMultipleUrl() {
        return HttpUrlManager.Companion.getInstance().getMDomainNameHub().size()>1;
    }

    public <A> A createApi(Class<A> apiClass) {
        return createWrapperService(apiClass);
    }

    public <A> A createApi(Class<A> apiClass,OkHttpClient client) {
        return getRetrofitService(apiClass,client);
    }

    /**
     * 根据 https://zhuanlan.zhihu.com/p/40097338 对 Retrofit 进行的优化
     *
     * @param serviceClass ApiService class
     * @param <T> ApiService class
     * @return ApiService
     */
    private <T> T createWrapperService(Class<T> serviceClass) {
        // 二次代理
        return (T) Proxy.newProxyInstance(serviceClass.getClassLoader(),
                new Class<?>[]{serviceClass}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, @Nullable Object[] args)
                            throws Throwable {
                        // 此处在调用 serviceClass 中的方法时触发

                        if (method.getReturnType() == Observable.class) {
                            // 如果方法返回值是 Observable 的话，则包一层再返回，
                            // 只包一层 defer 由外部去控制耗时方法以及网络请求所处线程，
                            // 如此对原项目的影响为 0，且更可控。
                            return Observable.defer(() -> {
                                final T service = getRetrofitService(serviceClass);
                                // 执行真正的 Retrofit 动态代理的方法
                                return ((Observable) getRetrofitMethod(service, method)
                                        .invoke(service, args));
                            });
                        } else if (method.getReturnType() == Single.class) {
                            // 如果方法返回值是 Single 的话，则包一层再返回。
                            return Single.defer(() -> {
                                final T service = getRetrofitService(serviceClass);
                                // 执行真正的 Retrofit 动态代理的方法
                                return ((Single) getRetrofitMethod(service, method)
                                        .invoke(service, args));
                            });
                        }

                        // 返回值不是 Observable 或 Single 的话不处理。
                        final T service = getRetrofitService(serviceClass);
                        return getRetrofitMethod(service, method).invoke(service, args);
                    }
                });
    }


    private <T> T getRetrofitService(Class<T> serviceClass) {
        String apiKey = getApiKey(serviceClass);
        T retrofitService = (T) retrofitServiceCache.get(apiKey);

        if (retrofitService == null) {
            if(retrofit==null) {
                String defaultBaseUrl = HttpUrlManager.Companion.getInstance().getUrl();

                retrofit = new RetrofitBuilder()
                        .setBaseUrl(defaultBaseUrl)
                        .setCallAdapterFactory(callAdapterFactory)
                        .setConverterFactory(converterFactory)
                        .setOkHttpClient(okHttpClient)
                        .build();
            }

            retrofitService = retrofit.create(serviceClass);

            retrofitServiceCache.put(apiKey,retrofitService);
        }
        return getRetrofitService(serviceClass,okHttpClient);
    }

    /**
     * 自定义 okHttpClient
     * @param serviceClass
     * @param okHttpClient
     * @param <T>
     * @return
     */
    private <T> T getRetrofitService(Class<T> serviceClass,OkHttpClient okHttpClient) {
        String apiKey = getApiKey(serviceClass);
        T retrofitService = (T) retrofitServiceCache.get(apiKey);

        if (retrofitService == null) {
            String defaultBaseUrl = HttpUrlManager.Companion.getInstance().getUrl();

            Retrofit  retrofit = new RetrofitBuilder()
                    .setBaseUrl(defaultBaseUrl)
                    .setCallAdapterFactory(callAdapterFactory)
                    .setConverterFactory(converterFactory)
                    .setOkHttpClient(okHttpClient)
                    .build();

            retrofitService = retrofit.create(serviceClass);

            retrofitServiceCache.put(apiKey,retrofitService);
        }

        return retrofitService;
    }


    private static <A> String getApiKey(Class<A> apiClass) {
        return String.format("_%s",  apiClass);
    }

    private <T> Method getRetrofitMethod(T service, Method method) throws NoSuchMethodException {
        return service.getClass().getMethod(method.getName(), method.getParameterTypes());
    }


}
