package de.beyondsoft.ownchat.data.api;

import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import org.spongycastle.asn1.pkcs.RSAPublicKey;
import org.spongycastle.openssl.PEMWriter;

import de.beyondsoft.ownchat.BewoplanerApplication;
import de.beyondsoft.ownchat.BuildConfig;
import de.beyondsoft.ownchat.data.AppPrefsConstants;
import de.beyondsoft.ownchat.data.prefs.StringPreference;
import de.beyondsoft.ownchat.di.P3DSEndpoint;
import de.beyondsoft.ownchat.di.scopes.ApplicationScope;
import de.beyondsoft.ownchat.utils.Constants;

import java.io.File;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.inject.Named;
import javax.net.ssl.SSLHandshakeException;

import dagger.Module;
import dagger.Provides;
import okhttp3.Cache;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.CallAdapter;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
import rx.schedulers.Schedulers;

import java.io.StringWriter;


import static de.beyondsoft.ownchat.utils.Constants.LOG_TAG_NETWORK_INTERCEPTOR;


@Module
public class ApiModule {

    @Provides
    @ApplicationScope
    Cache provideCache(BewoplanerApplication app) {
        File httpCacheDirectory = new File(app.getCacheDir().getAbsolutePath(), "HttpCache");
        return new Cache(httpCacheDirectory, Constants.DISK_CACHE_SIZE);
    }

    @Provides
    @ApplicationScope
    HttpLoggingInterceptor provideLoggingInterceptor() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        if (BuildConfig.DEBUG) {
            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        } else {
            interceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
        }

        return interceptor;
    }

    @Provides
    @ApplicationScope
    Interceptor provideHeaderInterceptor(@Named(AppPrefsConstants.LOGIN_TOKEN) final StringPreference token, @Named(AppPrefsConstants.ACCOUNT_NO) final StringPreference customerId) {
        return chain -> {
            final boolean isHeaderSet = token.isSet() || customerId.isSet();
            Request original = chain.request();
            Request.Builder builder = original.newBuilder();
            if (token.isSet()) {
                builder.addHeader("Token", token.get());
            }

            if (customerId.isSet()) {
                builder.addHeader("CustomerID", customerId.get());
            }

            if (isHeaderSet) {
                return chain.proceed(builder.build());
            }

            return chain.proceed(original.newBuilder().build());
        };
    }

    @Provides
    @ApplicationScope
    @P3DSEndpoint
    OkHttpClient provideP3DSOkHttpClient(Cache cache, HttpLoggingInterceptor loggingInterceptor, Interceptor interceptor) {
        return new OkHttpClient.Builder()
                .cache(cache)
                .connectTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .addInterceptor(loggingInterceptor)
                .addInterceptor(interceptor)
                .addNetworkInterceptor(chain -> {
                    Request request = chain.request();

                    HttpUrl url = request.url();

                    List<Certificate> certificates = chain.connection().handshake().peerCertificates();

                    boolean isVerified = false;

                    if(url.toString().contains(".ownchat.de")) {
                        if(certificates.size() < 2) {
                            throw new SSLHandshakeException("Insufficient number of certificates");
                        }

                        X509Certificate cert1 = (X509Certificate) certificates.get(0);
                        X509Certificate cert2 = (X509Certificate) certificates.get(1);

                        String cert1_subject_name = cert1.getSubjectX500Principal().getName();
                        String cert1_issuer_name = cert1.getIssuerX500Principal().getName();

                        String cert2_subject_name = cert2.getSubjectX500Principal().getName();

                        StringWriter cert2_writer = new StringWriter();
                        PEMWriter cert2_pemWriter = new PEMWriter(cert2_writer);

                        cert2_pemWriter.writeObject(cert2.getPublicKey());
                        cert2_pemWriter.flush();
                        cert2_pemWriter.close();

                        if(cert1_subject_name.contains(".ownchat.de") && cert1_issuer_name.contains("CN=Let's Encrypt Authority X3") && cert2_subject_name.contains("CN=Let's Encrypt Authority X3")) {
                            String cert2_writerAsString = cert2_writer.toString();
                            String cert2_base64EncodedKey = cert2_writerAsString.split("-----BEGIN PUBLIC KEY-----")[1].split("-----END PUBLIC KEY-----")[0].replaceAll(System.getProperty("line.separator"), "");

                            isVerified = cert2_base64EncodedKey.equals(Constants.PUBLIC_KEY);
                        } else {
                            Log.e(LOG_TAG_NETWORK_INTERCEPTOR, "SSL certificates are invalid!");
                            throw new SSLHandshakeException("Invalid SSL certificates");
                        }
                    }

                    if(!isVerified) {
                        Log.e(LOG_TAG_NETWORK_INTERCEPTOR, "SSL certificates are invalid!");
                        throw new SSLHandshakeException("Invalid SSL certificates");
                    }

                    return chain.proceed(request);
                })
                .build();
    }

    @Provides
    @ApplicationScope
    CallAdapter.Factory provideCallAdapterFactory() {
        return RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io());
    }

    @Provides
    @ApplicationScope
    Gson provideGson() {
        return new GsonBuilder()
                .setDateFormat("yyyy-MM-dd HH:mm:ss")
                .create();
    }

    @Provides
    @ApplicationScope
    Converter.Factory provideConverterFactory(Gson gson) {
        return GsonConverterFactory.create(gson);
    }

    @Provides
    @ApplicationScope
    @P3DSEndpoint
    Retrofit provideP3DSRetrofit(@P3DSEndpoint final OkHttpClient okHttpClient,
                                 @Named(AppPrefsConstants.API_ENDPOINT) final StringPreference endpoint,
                                 final Converter.Factory converter,
                                 final CallAdapter.Factory adapterFactory) {
        return new Retrofit.Builder()
                .baseUrl(endpoint.get() == null ? Constants.BEWOPLANER_ENDPOINT : endpoint.get())
                .addCallAdapterFactory(adapterFactory)
                .addConverterFactory(converter)
                .client(okHttpClient)
                .build();
    }
}