package twitch.instrumentorum.project.builds

import jetbrains.buildServer.configs.kotlin.v2018_2.ErrorConsumer
import jetbrains.buildServer.configs.kotlin.v2018_2.TeamCityDsl
import twitch.instrumentorum.BuildGroup
import twitch.instrumentorum.InstrumBuild
import twitch.instrumentorum.InstrumProject
import twitch.instrumentorum.project.steps.dockerBuild
import twitch.instrumentorum.project.steps.dockerPull
import twitch.instrumentorum.project.steps.dockerPush

@TeamCityDsl
class DockerBuildAndPush(name: String, buildGroup: BuildGroup) : DockerAwareBuild(name, buildGroup) {
    internal val tags = arrayListOf<String>()
    internal val tagNames
        get() = tags.joinToString("\n") { "$ecrDomain/$ecrRepository:$it" }

    var buildContextDir: String? = null
    var cacheFrom: String? = null
    var dockerfilePath: String = "Dockerfile"
    var dockerfileContent: String = ""
    var ecrRepository: String = "%ecr.repo%"
    var targetStage: String? = null
    var dockerArgs: String = ""

    constructor(name: String, buildGroup: BuildGroup, init: DockerBuildAndPush.() -> Unit) : this(name, buildGroup) {
        init()
    }

    override fun finalize() {
        super.finalize()

        tags.removeIf(String::isBlank)
        if (tags.isEmpty()) {
            tag("%git.commit.short%")
        }

        val cacheFrom = this.cacheFrom
        if (cacheFrom != null) {
            dockerPull(cacheFrom)
        }

        dockerBuild(
            tagNames,
            dockerArgs = *arrayOf(dockerArgs),
            path = dockerfilePath,
            target = targetStage,
            cacheFrom = cacheFrom,
            contextDir = buildContextDir,
            dockerfileContent = dockerfileContent
        )

        dockerPush(tagNames, cleanupImages)
    }

    override fun validate(consumer: ErrorConsumer) {
        super.validate(consumer)

        if (ecrRepository.isBlank()) {
            consumer.consumePropertyError("ecrRepository", "mandatory 'ecrRepository' property is not specified")
        }

        when {
            tags.isEmpty() -> {
                consumer.consumePropertyError("tags", "mandatory 'tags' property has no members")
            }
            tags.any { !it.matches("^(?:\\w|%[\\w.]+%)(?:[\\w.-]|%[\\w.]+%)*$".toRegex()) } -> {
                val message = "members of 'tags' property must conform to '^(?:\\w|%[\\w.]+%)(?:[\\w.-]|%[\\w.]+%)*\$'"
                consumer.consumePropertyError("tags", message)
            }
            tags.any { !it.contains('%') && it.length > 128 } -> {
                val message = "members of 'tags' property must be 128 characters or less if they do not reference a parameter"
                consumer.consumePropertyError("tags", message)
            }
        }
    }

    // Feels kinda funky but kotlin doesn't have another way to do a write only setter
    var tag: String
        get() = throw UnsupportedOperationException()
        set(value) {
            tags.add(value)
        }

    fun tag(tag: String) =
        tags.add(tag)
}

@TeamCityDsl
fun BuildGroup.dockerBuildAndPush(buildName: String, init: DockerBuildAndPush.() -> Unit): InstrumBuild =
    DockerBuildAndPush(buildName, this, init)

@TeamCityDsl
fun InstrumProject.dockerBuildAndPush(buildName: String, init: DockerBuildAndPush.() -> Unit): InstrumBuild =
    BuildGroup(this, false).dockerBuildAndPush(buildName, init)
