package im.dlg.botsdk;

import im.dlg.botsdk.internal.InternalBot;
import im.dlg.botsdk.utils.NetUtils;
import io.grpc.DecompressorRegistry;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.File;
import java.security.Security;
import java.util.concurrent.CompletableFuture;

import static java.util.concurrent.TimeUnit.*;
import static org.asynchttpclient.Dsl.asyncHttpClient;

public class BotSystem {

    private final ManagedChannel channel;
    private final AsyncHttpClient asyncHttpClient;
    private final boolean anonymousAuth;

    private BotSystem(ManagedChannel channel, AsyncHttpClient asyncHttpClient, boolean anonymousAuth) {
        this.channel = channel;
        this.asyncHttpClient = asyncHttpClient;
        this.anonymousAuth = anonymousAuth;
    }

    public static BotSystem createSystem(BotSystemConfig config) throws Exception {
        AsyncHttpClient asyncHttpClient = createAsyncHttpClient(config);
        ManagedChannel channel = createChannel(config);
        boolean anonymousAuth = config.getCertPath() != null && config.getCertPassword() != null;

        return new BotSystem(channel, asyncHttpClient, anonymousAuth);
    }

    private static AsyncHttpClient createAsyncHttpClient(BotSystemConfig config) throws Exception {
        DefaultAsyncHttpClientConfig.Builder builder = new DefaultAsyncHttpClientConfig.Builder();

        if (config.getCertPath() != null && config.getCertPassword() != null) {
                File certFile = new File(config.getCertPath());

                SslContext sslContext = SslContextBuilder.forClient()
                        .keyManager(NetUtils.createKeyFactory(certFile, config.getCertPassword()))
                        .build();

                builder.setSslContext(sslContext);
        }

        return asyncHttpClient(builder);
    }

    private static ManagedChannel createChannel(BotSystemConfig config) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        NettyChannelBuilder nettyChannelBuilder = (NettyChannelBuilder) ManagedChannelBuilder
                .forAddress(config.getHost(), config.getPort())
                .idleTimeout(15, SECONDS)
                .keepAliveTime(30, SECONDS);

        if (config.getCertPath() != null && config.getCertPassword() != null) {
            File certFile = new File(config.getCertPath());

            SslContext sslContext = GrpcSslContexts.forClient()
                    .keyManager(NetUtils.createKeyFactory(certFile, config.getCertPassword()))
                    .build();

            nettyChannelBuilder.sslContext(sslContext);
        }

        if (!config.isSecure()) {
            nettyChannelBuilder.usePlaintext();
        }

        if (!config.isCompression()) {
            nettyChannelBuilder.decompressorRegistry(DecompressorRegistry.emptyInstance());
        }

        return nettyChannelBuilder.build();
    }

    public CompletableFuture<Bot> startBot(BotConfig config) {
        InternalBot internalBot = new InternalBot(channel, config, anonymousAuth);

        return internalBot.start()
                .thenApply(v -> new Bot(channel, internalBot, asyncHttpClient));
    }

    public CompletableFuture<Bot> startBotWithToken(String name, String token) {
        BotConfig botConfig = BotConfig.Builder.newBuilder()
                .withName(name)
                .withCredentials(new BotCredentials(BotCredentials.Method.TOKEN, token))
                .build();

        return startBot(botConfig);
    }

    public CompletableFuture<Bot> startBotWithPassword(String name, String password) {
        BotConfig botConfig = BotConfig.Builder.newBuilder()
                .withName(name)
                .withCredentials(new BotCredentials(BotCredentials.Method.PASSWORD, password))
                .build();

        return startBot(botConfig);
    }

}