package io.delphiplatform.api.integration.client;

import com.google.api.gax.core.NoCredentialsProvider;
import com.google.api.gax.grpc.GrpcTransportChannel;
import com.google.api.gax.rpc.FixedTransportChannelProvider;
import com.google.api.gax.rpc.NotFoundException;
import com.google.api.gax.rpc.TransportChannelProvider;
import com.google.cloud.bigtable.admin.v2.BigtableTableAdminClient;
import com.google.cloud.bigtable.admin.v2.models.CreateTableRequest;
import com.google.cloud.bigtable.admin.v2.models.Table;
import com.google.cloud.bigtable.admin.v2.stub.BigtableTableAdminStubSettings;
import com.google.cloud.bigtable.admin.v2.stub.EnhancedBigtableTableAdminStub;
import com.google.cloud.bigtable.data.v2.BigtableDataClient;
import com.google.cloud.bigtable.data.v2.BigtableDataSettings;
import com.google.cloud.bigtable.data.v2.models.RowMutation;
import com.google.protobuf.ByteString;

import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;

import io.delphiplatform.api.integration.dto.RowDto;
import io.delphiplatform.api.integration.util.BigtableUtils;
import io.delphiplatform.api.integration.util.EnvUtils;
import io.delphiplatform.api.v3.bigtable.reader.BigtableEntityReader;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

/**
 * Dedicated BT client to be used from tests. Separate from BT client that is used by API
 */
@Component
public class TestBigtableClient {

    public static String PROJECT_ID = "test-project-id";
    public static String INSTANCE_ID = "test-instance-id";

    private ManagedChannel channel = null;
    private BigtableDataClient client = null;
    private BigtableTableAdminClient adminClient = null;

    @PreDestroy
    public void destroy() {
        if (channel != null) {
            channel.shutdown();
        }
        if (client != null) {
            client.close();
        }
        if (adminClient != null) {
            adminClient.close();
        }
    }

    public BigtableDataClient getClient() {
        if (client != null) {
            return client;
        }

        try {
            return client = BigtableDataClient.create(
                BigtableDataSettings
                    .newBuilderForEmulator(
                        EnvUtils.getEnvVar(EnvUtils.ENV_VAR_TEST_BT_HOST),
                        EnvUtils.getEnvVarInt(EnvUtils.ENV_VAR_TEST_BT_PORT))
                    .setProjectId(PROJECT_ID)
                    .setInstanceId(INSTANCE_ID)
                    .build()
            );
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public BigtableTableAdminClient getAdminClient() {
        if (adminClient != null) {
            return adminClient;
        }

        channel = ManagedChannelBuilder.forTarget(EnvUtils.getEnvVar(EnvUtils.ENV_VAR_TEST_BT_ENDPOINT))
            .usePlaintext().build();

        TransportChannelProvider channelProvider = FixedTransportChannelProvider.create(
            GrpcTransportChannel.create(channel));
        NoCredentialsProvider credentialsProvider = NoCredentialsProvider.create();

        try {
            EnhancedBigtableTableAdminStub stub = EnhancedBigtableTableAdminStub.createEnhanced(
                BigtableTableAdminStubSettings
                    .newBuilder()
                    .setTransportChannelProvider(channelProvider)
                    .setCredentialsProvider(credentialsProvider)
                    .build()
            );

            return adminClient = BigtableTableAdminClient.create(PROJECT_ID, INSTANCE_ID, stub);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Table createTable(String tableName) {
        try {
            return getAdminClient().createTable(CreateTableRequest.of(tableName)
                .addFamily(BigtableEntityReader.meta) //all families from BigtableEntityReader
                .addFamily(BigtableEntityReader.metrics)
                .addFamily(BigtableEntityReader.pos)
            );
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Table reCreateTable(String tableName) {
        tryDeleteTable(tableName);

        try {
            return createTable(tableName);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void deleteTable(String tableId) {
        getAdminClient().deleteTable(tableId);
    }

    /**
     * @param tableId seems to always be equal to table name
     */
    public void tryDeleteTable(String tableId) {
        try {
            deleteTable(tableId);
        } catch (NotFoundException e) {
            //expected when called from very 1st test that uses thus table
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void sendMutation(String tableName, String rowKey, RowDto row) {
        RowMutation mutation = RowMutation.create(tableName, rowKey);

        row.getCells().forEach(col -> {
            Object val = col.getValue();

            if (val == null) {
                return;
            }

            if (val instanceof String) {
                mutation.setCell(col.getFamily(), col.getName(), (String) val);
            } else if (val instanceof Long) {
                mutation.setCell(col.getFamily(), col.getName(), (Long) val);
            } else if (val instanceof ByteString) {
                mutation.setCell(col.getFamily(), BigtableUtils.toByteString(col.getName()), (ByteString) val);
            } else {
                throw new IllegalArgumentException("bad value type in column dto: " + col);
            }
        });

        try {
            getClient().mutateRow(mutation);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
