package com.atlassian.bitbucket.mesh;

import com.atlassian.bitbucket.mesh.app.BuildInfo;
import com.atlassian.bitbucket.mesh.cluster.WiredClusterNode;
import com.atlassian.bitbucket.mesh.com.atlassian.bitbucket.mesh.SimpleTransactionManager;
import com.atlassian.bitbucket.mesh.config.ApplicationConfig;
import com.atlassian.bitbucket.mesh.execution.ExecutionManager;
import com.atlassian.bitbucket.mesh.git.GitAgent;
import com.atlassian.bitbucket.mesh.git.builder.GitProcessBuilderFactory;
import com.atlassian.bitbucket.mesh.git.hook.GitHookService;
import com.atlassian.bitbucket.mesh.io.MoreFiles;
import com.atlassian.bitbucket.mesh.layout.DestroyableStorageLayout;
import com.atlassian.bitbucket.mesh.layout.HomeSubdirectoryResolver;
import com.atlassian.bitbucket.mesh.network.ChannelRegistry;
import com.atlassian.bitbucket.mesh.network.MeshManager;
import com.atlassian.bitbucket.mesh.repository.*;
import com.atlassian.bitbucket.mesh.test.ExecutionContextRule;
import com.atlassian.bitbucket.mesh.test.ExpectedEvents;
import com.atlassian.bitbucket.mesh.test.cluster.MeshClusterManager;
import com.atlassian.bitbucket.mesh.test.cluster.MeshControlPlane;
import com.atlassian.bitbucket.mesh.test.cluster.StandaloneMeshControlPlane;
import com.atlassian.bitbucket.mesh.test.context.MapRepositoryCache;
import com.atlassian.bitbucket.mesh.test.context.RepositoryCache;
import com.atlassian.bitbucket.mesh.util.Version;
import com.google.common.collect.ImmutableList;
import io.grpc.ManagedChannel;
import io.grpc.netty.NettyChannelBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.transaction.PlatformTransactionManager;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Provides dependencies required by the various {@link AbstractWiredTest wired tests}.
 */
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) //Proxying for gRPC services must be done by class
@PropertySources({
        @PropertySource("classpath:application-private.properties"),
        @PropertySource("classpath:application-public.properties"),
        @PropertySource("classpath:application-test.properties"),
        @PropertySource("classpath:application-control-plane.properties")
})
@ImportResource("classpath:mesh-listeners.xml") //See comment in file for why this is XML
public class MeshTestWiring {

    @Bean //This overrides the MeshCoreWiring version, which also disables the home directory property checks
    public ApplicationDirs applicationDirs() throws IOException {
        Path home = Files.createTempDirectory("home").toAbsolutePath();
        Path sharedHome = MoreFiles.mkdir(home, "shared");

        //Set a system property for the home directory to allow referring to it from properties
        System.setProperty("mesh.home", home.toString());

        return new ApplicationDirs(home, sharedHome, ImmutableList.of(home.resolve("store")));
    }

    @Bean
    public ExecutionContextRule executionContextRule(ExecutionManager executionManager) {
        return new ExecutionContextRule(executionManager);
    }

    @Bean
    public ExpectedEvents expectedEvents() {
        return new ExpectedEvents();
    }

    @Bean
    public WiredClusterNode localMeshNode(ApplicationConfig config,
                                          MeshControlPlane controlPlane,
                                          @Value("${grpc.server.port}") int grpcServerPort) {
        //RPC URL matches the managedChannel() below
        return new WiredClusterNode(config, controlPlane, "http://localhost:" + grpcServerPort);
    }

    @Bean(destroyMethod = "shutdown")
    public ManagedChannel managedChannel(@Value("${grpc.server.port}") int grpcServerPort) {
        return NettyChannelBuilder.forAddress(new InetSocketAddress("", grpcServerPort))
                .executor(Executors.newCachedThreadPool(new CustomizableThreadFactory("mesh-grpc-client")))
                .usePlaintext() //The NettyChannel defaults to h2 (TLS); this switches to h2c
                .build();
    }

    @Bean
    public ManagedDataStore managedDataStore(ApplicationDirs applicationDirs, BuildInfo buildInfo,
                                             @Lazy GitHookService hookService) {
        return new ManagedDataStore(new Version(buildInfo.getBuildVersion()), hookService,
                applicationDirs.getDataDirs());
    }

    @Bean
    public MeshClusterManager meshClusterManager(MeshControlPlane controlPlane,
                                                 ExecutorService executorService) throws IOException {
        Path homesDir = Files.createTempDirectory("mesh-homes").toAbsolutePath();

        return new MeshClusterManager(controlPlane, executorService, homesDir, null);
    }

    @Bean(destroyMethod = "destroy")
    public StandaloneMeshControlPlane meshControlPlane(
            @Value("${mesh.control-plane.partition-count:64}") int partitions) {
        return new StandaloneMeshControlPlane(partitions);
    }

    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new SimpleTransactionManager();
    }

    @Bean
    public RepositoryAttributeDao repositoryAttributeDao() {
        return new SimpleRepositoryAttributeDao();
    }

    @Bean
    public RepositoryCache repositoryCache() {
        return new MapRepositoryCache();
    }

    @Bean(destroyMethod = "destroy")
    public DestroyableStorageLayout storageLayout(ApplicationDirs applicationDirs,
                                                  HomeSubdirectoryResolver homeSubdirectoryResolver) {
        return new DestroyableStorageLayout(applicationDirs, homeSubdirectoryResolver);
    }

    @Bean
    public TestHelper testHelper(GitAgent agent, GitProcessBuilderFactory builderFactory,
                                 ChannelRegistry channelRegistry, MeshClusterManager clusterManager,
                                 ExecutionManager executionManager, MeshManager meshManager,
                                 RepositoryManager repositoryManager) {
        return new TestHelper(agent, builderFactory, channelRegistry, clusterManager,
                executionManager, meshManager, repositoryManager);
    }

    @Bean
    public WorkTreeStateDao workTreeStateDao() {
        return new SimpleWorkTreeStateDao();
    }
}
