/*
 * Copyright Thoughtworks, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.thoughtworks.go.build.docker.BuildDockerImageTask
import com.thoughtworks.go.build.docker.Distro
import com.thoughtworks.go.build.docker.DistroVersion
import com.thoughtworks.go.build.docker.ImageType
import groovy.json.JsonOutput
import io.github.resilience4j.retry.Retry
import io.github.resilience4j.retry.RetryConfig

import java.time.Duration

buildscript {
  dependencies {
    classpath project.deps.resilience4jRetry
  }
}

plugins {
  id 'base'
}

subprojects {
  apply plugin: 'base'
  layout.buildDirectory = parent.layout.buildDirectory.dir(project.name).get()

  // detect the distro name and version from the project name.
  // For e.g. `ubuntu-20.04` will map to `Distro.ubuntu` and version `20.04` as returned by `Distro.ubuntu.supportedVersions`
  def distroName = project.name.split('-').first()
  Distro distro = Distro.valueOf(distroName)

  if (!project.name.startsWith("${distro.name()}-")) {
    throw new GradleException("Did you initialize the project ${project.path} with the wrong distro(${distro.name()})?".toString())
  }

  def distroVersionOnProject = project.name.replaceFirst("${distro.name()}-", "")
  DistroVersion distroVersion = distro.getVersion(distroVersionOnProject)

  def docker = tasks.register("docker", BuildDockerImageTask.class) { BuildDockerImageTask task ->
    task.dependsOn ':docker:initializeBuildx'

    if (project.hasProperty('dockerBuildLocalZip')) {
      task.dependsOn ':installers:agentGenericZip'
      task.artifactZip.set(rootProject.project(':installers').tasks.named('agentGenericZip').flatMap { Zip zt ->  zt.archiveFile } as Provider<? extends RegularFile>)
    } else if (project.hasProperty('dockerbuildAgentZipLocation')) {
      task.artifactZip.set(project.file(project.dockerbuildAgentZipLocation))
    } else {
      task.doFirst {
        throw new GradleException("You must specify either -PdockerBuildLocalZip or -PdockerbuildAgentZipLocation=/path/to/agent.zip")
      }
    }

    task.description = "Generate the dockerfile for GoCD agent running on ${distro.name()} v${distroVersion.version}"
    task.group = BasePlugin.BUILD_GROUP

    task.distro = distro
    task.distroVersion = distroVersion
    task.tiniVersion = project.versions.tini
    task.imageType = ImageType.agent
    task.outputDir.set(parent.layout.buildDirectory.dir("docker") as Provider<Directory>)

    def licenseSourceFile = rootProject.file("LICENSE")

    task.templateHelper = {
      deleteGitRepoDirectoryContents()

      fileOps.copy {
        from licenseSourceFile
        into gitRepoDirectory
      }

      task.writeTemplateToFile("README.md.ftl", "README.md")

      [
        'agent-bootstrapper-logback-include.xml',
        'agent-launcher-logback-include.xml',
        'agent-logback-include.xml',
      ].forEach { eachFile ->
        task.resolveGitRepoFileFor(eachFile).bytes = BuildDockerImageTask.class.getResource("/gocd-docker-agent/${eachFile}").bytes
      }

      [
        'docker-entrypoint.sh',
      ].forEach { eachFile ->
        def file = task.resolveGitRepoFileFor(eachFile)
        file.bytes = BuildDockerImageTask.class.getResource("/gocd-docker-agent/${eachFile}").bytes
        file.setExecutable(true, false)
      }

      if (distro == Distro.docker) {
        def file = task.resolveGitRepoFileFor("run-docker-daemon.sh")
        file.bytes = BuildDockerImageTask.class.getResource("/gocd-docker-agent/run-docker-daemon.sh").bytes
        file.setExecutable(true, false)

        file = task.resolveGitRepoFileFor("dockerd-sudo")
        file.bytes = BuildDockerImageTask.class.getResource("/gocd-docker-agent/dockerd-sudo").bytes
      }
    }

    // test image
    task.verifyHelper = {
      def cleanContainer = { OutputStream errorStream = System.err ->
        execOps.exec {
          commandLine = ["docker", "rm", "--force", docker.dockerImageName]
          standardOutput = System.out
          errorOutput = errorStream
        }
      }

      cleanContainer.call(OutputStream.nullOutputStream()) // Clean-up after any previous aborted runs

      // daemonize the container
      execOps.exec {
        def additionalFlags = distro == Distro.docker ? ["--privileged"] : []
        commandLine = ["docker", "run", "-e", "GO_SERVER_URL=http://localhost:8153/go", "-d", "--name", docker.dockerImageName] + additionalFlags + [docker.imageNameWithTag] as List<String>
        standardOutput = System.out
        errorOutput = System.err
      }

      def start = System.currentTimeMillis()
      try {
        Retry.of("wait-for-container-to-start",
          RetryConfig.custom().maxAttempts(40).waitDuration(Duration.ofMillis(500)).failAfterMaxAttempts(true).build()
        ).executeRunnable {
          task.verifyProcessInContainerStarted(
            "lib/agent-bootstrapper.jar -serverUrl http://localhost:8153/go",
            /Connect to localhost:8153.*Connection refused/
          )
        }

        distro.additionalVerifyCommands.each { command ->
          execOps.exec {
            commandLine = ["docker", "exec", docker.dockerImageName] + command
            standardOutput = System.out
            errorOutput = System.err
          }
        }
      } finally {
        logger.lifecycle("Took ${System.currentTimeMillis() - start} ms to verify [${docker.dockerImageName}] container started.")
        // remove the container
        cleanContainer.call()
      }
    }
  }
  assemble.dependsOn(docker)
}

tasks.register('generateManifest') {
  def dockerTasks = subprojects.tasks*.named('docker') as Collection<TaskProvider<? extends BuildDockerImageTask>>
  def manifest = layout.buildDirectory.file("docker/manifest.json")
  outputs.file(manifest)

  doLast {
    def meta = []
    dockerTasks.collect { it.get() }.each { dockerTask ->
      meta << [
        file     : "${dockerTask.imageTarFile.name}",
        format   : 'oci',
        imageName: dockerTask.dockerImageName,
        tag      : dockerTask.imageTag,
        platforms: dockerTask.supportedPlatforms
      ]
    }

    def jsonFile = manifest.get().asFile
    jsonFile.parentFile.mkdirs()
    jsonFile.withWriter("utf-8") { Writer out ->
      out << JsonOutput.toJson(meta)
    }
  }
}

assemble.dependsOn(subprojects.tasks*.named('assemble'))
assemble.dependsOn(generateManifest)
