import org.elasticsearch.gradle.Architecture
import org.elasticsearch.gradle.ElasticsearchDistribution.Flavor
import org.elasticsearch.gradle.LoggedExec
import org.elasticsearch.gradle.VersionProperties
import org.elasticsearch.gradle.docker.DockerBuildTask
import org.elasticsearch.gradle.info.BuildParams
import org.elasticsearch.gradle.testfixtures.TestFixturesPlugin

apply plugin: 'elasticsearch.standalone-rest-test'
apply plugin: 'elasticsearch.test.fixtures'
apply plugin: 'elasticsearch.distribution-download'

testFixtures.useFixture()

configurations {
  dockerPlugins
  aarch64DockerSource
  dockerSource
  aarch64OssDockerSource
  ossDockerSource
}

dependencies {
  aarch64DockerSource project(path: ":distribution:archives:linux-aarch64-tar")
  dockerSource project(path: ":distribution:archives:linux-tar")
  aarch64OssDockerSource project(path: ":distribution:archives:oss-linux-aarch64-tar")
  ossDockerSource project(path: ":distribution:archives:oss-linux-tar")
}

ext.expansions = { architecture, oss, local ->
  String classifier
  if (local) {
    switch (architecture) {
      case "aarch64":
        classifier = "linux-aarch64"
        break
      case "x64":
        classifier = "linux-x86_64"
        break
      default:
        throw new IllegalArgumentException("Unrecognized architecture [" + architecture + "], must be one of (aarch64|x64)")
    }
  } else {
    /* When sourcing the Elasticsearch build remotely, the same Dockerfile needs
     * to be able to fetch the artifact for any supported platform. We can't make
     * the decision here. Bash will interpolate the `arch` command for us. */
    classifier = "linux-\$(arch)"
  }

  final String elasticsearch = "elasticsearch-${oss ? 'oss-' : ''}${VersionProperties.elasticsearch}-${classifier}.tar.gz"

  /* Both the following Dockerfile commands put the resulting artifact at
   * the same location, regardless of classifier, so that the commands that
   * follow in the Dockerfile don't have to know about the runtime
   * architecture. */
  String sourceElasticsearch
  if (local) {
    sourceElasticsearch = "COPY $elasticsearch /opt/elasticsearch.tar.gz"
  } else {
    sourceElasticsearch = """
RUN curl --retry 8 -S -L \\
      --output /opt/elasticsearch.tar.gz \\
      https://artifacts.elastic.co/downloads/elasticsearch/$elasticsearch
"""
  }

  return [
    'build_date'          : BuildParams.buildDate,
    'git_revision'        : BuildParams.gitRevision,
    'license'             : oss ? 'Apache-2.0' : 'Elastic-License',
    'source_elasticsearch': sourceElasticsearch,
    'version'             : VersionProperties.elasticsearch
  ]
}

private static String buildPath(final String architecture, final boolean oss) {
  return "build/${"aarch64".equals(architecture) ? 'aarch64-' : ''}${oss ? 'oss-' : ''}docker"
}

private static String taskName(final String prefix, final String architecture, final boolean oss, final String suffix) {
  return "${prefix}${"aarch64".equals(architecture) ? 'Aarch64' : ''}${oss ? 'Oss' : ''}${suffix}"
}

project.ext {
  dockerBuildContext = { String architecture, boolean oss, boolean local ->
    copySpec {
      into('bin') {
        from project.projectDir.toPath().resolve("src/docker/bin")
      }

      into('config') {
        /*
         * Oss and default distribution can have different configuration, therefore we want to allow overriding the default configuration
         * by creating config files in oss or default build-context sub-modules.
         */
        from project.projectDir.toPath().resolve("src/docker/config")
        if (oss) {
          from project.projectDir.toPath().resolve("src/docker/config/oss")
        }
      }

      from(project.projectDir.toPath().resolve("src/docker/Dockerfile")) {
        expand(expansions(architecture, oss, local))
      }
    }
  }
}

void addCopyDockerContextTask(final String architecture, final boolean oss) {
  task(taskName("copy", architecture, oss, "DockerContext"), type: Sync) {
    expansions(architecture, oss, true).findAll { it.key != 'build_date' }.each { k, v ->
      inputs.property(k, { v.toString() })
    }
    into buildPath(architecture, oss)

    with dockerBuildContext(architecture, oss, true)

    if ("aarch64".equals(architecture)) {
      if (oss) {
        from configurations.aarch64OssDockerSource
      } else {
        from configurations.aarch64DockerSource
      }
    } else {
      if (oss) {
        from configurations.ossDockerSource
      } else {
        from configurations.dockerSource
      }
    }

    from configurations.dockerPlugins
  }
}

def createAndSetWritable(Object... locations) {
  locations.each { location ->
    File file = file(location)
    file.mkdirs()
    file.setWritable(true, false)
  }
}

task copyKeystore(type: Sync) {
  from project(':x-pack:plugin:core')
    .file('src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks')
  into "${buildDir}/certs"
  doLast {
    file("${buildDir}/certs").setReadable(true, false)
    file("${buildDir}/certs/testnode.jks").setReadable(true, false)
  }
}

elasticsearch_distributions {
  Architecture.values().each { eachArchitecture ->
    Flavor.values().each { distroFlavor ->
      "docker_$distroFlavor${ eachArchitecture == Architecture.AARCH64 ? '_aarch64' : '' }" {
        architecture = eachArchitecture
        flavor = distroFlavor
        type = 'docker'
        version = VersionProperties.getElasticsearch()
        failIfUnavailable = false // This ensures we don't attempt to build images if docker is unavailable
      }
    }
  }
}

preProcessFixture {
  dependsOn elasticsearch_distributions.docker_default, elasticsearch_distributions.docker_oss
  dependsOn copyKeystore
  doLast {
    // tests expect to have an empty repo
    project.delete(
      "${buildDir}/repo",
      "${buildDir}/oss-repo"
    )
    createAndSetWritable(
      "${buildDir}/repo",
      "${buildDir}/oss-repo",
      "${buildDir}/logs/default-1",
      "${buildDir}/logs/default-2",
      "${buildDir}/logs/oss-1",
      "${buildDir}/logs/oss-2"
    )
  }
}

processTestResources {
  from project(':x-pack:plugin:core')
    .file('src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks')
}

task integTest(type: Test) {
  outputs.doNotCacheIf('Build cache is disabled for Docker tests') { true }
  maxParallelForks = '1'
  include '**/*IT.class'
}

check.dependsOn integTest

void addBuildDockerImage(final String architecture, final boolean oss) {
  final Task buildDockerImageTask = task(taskName("build", architecture, oss, "DockerImage"), type: DockerBuildTask) {
    TaskProvider<Sync> copyContextTask = tasks.named(taskName("copy", architecture, oss, "DockerContext"))
    dependsOn(copyContextTask)
    dockerContext.fileProvider(copyContextTask.map { it.destinationDir })

    if (oss) {
      tags = [
        "docker.elastic.co/elasticsearch/elasticsearch-oss:${VersionProperties.elasticsearch}",
        "elasticsearch-oss:test"
      ]
    } else {
      tags = [
        "elasticsearch:${VersionProperties.elasticsearch}",
        "docker.elastic.co/elasticsearch/elasticsearch:${VersionProperties.elasticsearch}",
        "docker.elastic.co/elasticsearch/elasticsearch-full:${VersionProperties.elasticsearch}",
        "elasticsearch:test",
      ]
    }
  }
  buildDockerImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) }
  assemble.dependsOn(buildDockerImageTask)
}

for (final String architecture : ["aarch64", "x64"]) {
  for (final boolean oss : [false, true]) {
    addCopyDockerContextTask(architecture, oss)
    addBuildDockerImage(architecture, oss)
  }
}

// We build the images used in compose locally, but the pull command insists on using a repository
// thus we must disable it to prevent it from doing so.
// Everything will still be pulled since we will build the local images on a pull
if (tasks.findByName("composePull")) {
  tasks.composePull.enabled = false
}

/*
 * The export subprojects write out the generated Docker images to disk, so
 * that they can be easily reloaded, for example into a VM.
 */
subprojects { Project subProject ->
  if (subProject.name.endsWith('-export')) {
    apply plugin: 'distribution'

    final String architecture = subProject.name.contains('aarch64-') ? 'aarch64' : 'x64'
    final boolean oss = subProject.name.contains('oss-')

    def exportTaskName = taskName("export", architecture, oss, "DockerImage")
    def buildTaskName = taskName("build", architecture, oss, "DockerImage")
    def tarFile = "${parent.projectDir}/build/elasticsearch${"aarch64".equals(architecture) ? '-aarch64' : ''}${oss ? '-oss' : ''}_test.${VersionProperties.elasticsearch}.docker.tar"

    final Task exportDockerImageTask = task(exportTaskName, type: LoggedExec) {
      inputs.file("${parent.projectDir}/build/markers/${buildTaskName}.marker")
      executable 'docker'
      outputs.file(tarFile)
      args "save",
        "-o",
        tarFile,
        "elasticsearch${oss ? '-oss' : ''}:test"
    }

    exportDockerImageTask.dependsOn(parent.tasks.getByName(buildTaskName))

    exportDockerImageTask.onlyIf { Architecture.current().name().toLowerCase().equals(architecture) }

    artifacts.add('default', file(tarFile)) {
      type 'tar'
      name "elasticsearch${"aarch64".equals(architecture) ? '-aarch64' : ''}${oss ? '-oss' : ''}"
      builtBy exportTaskName
    }

    assemble.dependsOn exportTaskName
  }
}