package com.atlassian.bitbucket.mesh.git.builder;

import com.atlassian.bitbucket.mesh.config.ProcessConfig;
import com.atlassian.bitbucket.mesh.git.GitQuarantineRegistry;
import com.atlassian.bitbucket.mesh.git.binary.GitBinary;
import com.atlassian.bitbucket.mesh.layout.StorageLayout;
import com.atlassian.bitbucket.mesh.process.ProcessCreator;
import com.atlassian.bitbucket.mesh.repository.Repository;
import com.atlassian.bitbucket.mesh.repository.RepositoryManager;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcGitOptions;
import com.atlassian.bitbucket.mesh.rpc.v1.RpcGitTimeouts;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.Descriptors;
import com.google.protobuf.MessageOrBuilder;

import javax.annotation.Nonnull;
import java.util.List;

import static com.atlassian.bitbucket.mesh.rpc.util.ByteStringUtils.toStringsLazily;
import static java.util.Objects.requireNonNull;

public class DefaultGitProcessBuilderFactory implements GitProcessBuilderFactory {

    private final GitBinary binary;
    private final List<GitProcessBuilderConfigurer> configurers;
    private final ProcessConfig processConfig;
    private final ProcessCreator processCreator;
    private final GitQuarantineRegistry quarantineRegistry;
    private final RepositoryManager repositoryManager;
    private final StorageLayout storageLayout;

    public DefaultGitProcessBuilderFactory(GitBinary binary, GitProcessBuilderConfigurer[] configurers,
                                           ProcessConfig processConfig, ProcessCreator processCreator,
                                           GitQuarantineRegistry quarantineRegistry, RepositoryManager repositoryManager,
                                           StorageLayout storageLayout) {
        this.binary = binary;
        this.configurers = ImmutableList.copyOf(configurers);
        this.processConfig = processConfig;
        this.processCreator = processCreator;
        this.quarantineRegistry = quarantineRegistry;
        this.repositoryManager = repositoryManager;
        this.storageLayout = storageLayout;
    }

    @Nonnull
    @Override
    public GitProcessBuilder builder() {
        return builder((Repository) null);
    }

    @Nonnull
    @Override
    public GitProcessBuilder builder(@Nonnull MessageOrBuilder message) {
        Descriptors.Descriptor descriptor = requireNonNull(message, "message").getDescriptorForType();
        GitProcessBuilder builder = builder(maybeGetRepository(message, descriptor));

        //Conventions are enforced by AbstractGrpcServiceTest.testRequestConventions, which ensures if any of
        //these fields are present on a request type they are of the expected types. That build-time validation
        //should ensure these runtime casts are safe
        Descriptors.FieldDescriptor optionsField = descriptor.findFieldByName("options");
        if (optionsField != null && message.hasField(optionsField)) {
            applyOptions(builder, (RpcGitOptions) message.getField(optionsField));
        }

        Descriptors.FieldDescriptor timeoutsField = descriptor.findFieldByName("timeouts");
        if (timeoutsField != null && message.hasField(timeoutsField)) {
            builder.withTimeouts((RpcGitTimeouts) message.getField(timeoutsField));
        }

        return builder;
    }

    @Nonnull
    @Override
    public GitProcessBuilder builder(Repository repository) {
        GitProcessBuilder builder = new DefaultGitProcessBuilder(binary, processConfig, processCreator,
                quarantineRegistry, storageLayout, repository);
        for (GitProcessBuilderConfigurer configurer : configurers) {
            configurer.configure(builder);
        }

        return builder;
    }

    private void applyOptions(GitProcessBuilder builder, RpcGitOptions options) {
        options.getAlternatesList().stream()
                .map(repositoryManager::getById)
                .forEach(builder::alternate);

        toStringsLazily(options.getConfigMap()).forEach(builder::withConfiguration);
        toStringsLazily(options.getEnvironmentMap()).forEach(builder::withEnvironment);
    }

    private Repository maybeGetRepository(MessageOrBuilder message, Descriptors.Descriptor descriptor) {
        Descriptors.FieldDescriptor repositoryField = descriptor.findFieldByName("repository");
        if (repositoryField != null && message.hasField(repositoryField)) {
            String id = (String) message.getField(repositoryField);

            return repositoryManager.getById(id);
        }

        return null;
    }
}
