package twitch.instrumentorum.project.builds

import jetbrains.buildServer.configs.kotlin.v2018_2.ErrorConsumer
import jetbrains.buildServer.configs.kotlin.v2018_2.ParameterDisplay
import jetbrains.buildServer.configs.kotlin.v2018_2.buildSteps.script
import twitch.instrumentorum.BuildGroup
import twitch.instrumentorum.InstrumProject
import twitch.instrumentorum.enums.AWSRegion
import twitch.instrumentorum.exceptions.DuplicateRegion
import twitch.instrumentorum.helpers.assetText

class UpdateECSService(name: String, buildGroup: BuildGroup) : DockerAwareBuild(name, buildGroup) {
    internal data class RegionalData(val taskDefinition: String, var desiredCount: Int?)

    internal val updateRegions = mutableMapOf<AWSRegion, RegionalData>()

    var deployRole = ""
    var desiredCount = 2
    var dockerImage = "%ecr.domain%/%ecr.repo%:%git.commit.short%"
    var ecsAccountId = ""
    var ecsCluster = ""
    var ecsService = ""
    var ecsTaskFamily = "%ecs.service%"

    init {
        type = Type.DEPLOYMENT
    }

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

    override fun finalize() {
        super.finalize()

        textParam(
            "deploy.regions",
            updateRegions.keys.map(AWSRegion::code).joinToString(", "),
            "Target Regions Environments",
            "A comma separated list of AWS Regions to deploy to",
            ParameterDisplay.NORMAL,
            regex = "^[a-z]{3}(?:,\\s*[a-z]{3})*\$",
            validationMessage = "This must be a comma separated list of region airport codes"
        )

        textParam("deploy.role", deployRole, "Deploy IAM Role", "This role is assumed if provided", allowEmpty = true)
        textParam("ecs.account", ecsAccountId, "Target ECS AccountId")
        textParam("ecs.cluster", ecsCluster, "Target ECS Cluster")
        textParam("ecs.service", ecsService, "Target ECS Service Name")
        textParam("ecs.task.family", ecsTaskFamily, "ECS Task Family")
        textParam("docker.image", dockerImage, "Docker Image", "Name of the docker image to deploy")
        textParam("task.revisions", "{}", "Task Definition Versions")

        // Ensure each region has a desired count
        updateRegions.forEach { _, data -> data.desiredCount = data.desiredCount ?: desiredCount }

        // First do all the Task Definition Updates
        updateRegions.forEach { awsRegion, data ->
            steps.script {
                name = "Update Task Definition in ${awsRegion.code.toUpperCase()}"
                scriptContent = assetText("scripts/ecs_update_task_definition.rb")
                    .replace("\$TASK_DEFINITION", data.taskDefinition)
                    .replace("\$AWS_REGION_CODE", awsRegion.code)
                    .replace("\$AWS_REGION", awsRegion.region)
            }
        }

        // Now do all of the ECS Service updates
        updateRegions.forEach { awsRegion, data ->
            steps.script {
                name = "Update ECS Service in ${awsRegion.code.toUpperCase()}"
                scriptContent = assetText("scripts/ecs_update_service.rb")
                    .replace("\$DESIRED_COUNT", data.desiredCount.toString())
                    .replace("\$AWS_REGION_CODE", awsRegion.code)
                    .replace("\$AWS_REGION", awsRegion.region)
            }
        }

        // Now we verify the deployments
        // This is done sequentially instead of interspersed, but the tent pole shouldn't be affected too much.
        updateRegions.forEach { awsRegion, _ ->
            steps.script {
                name = "Verify ECS Service update in ${awsRegion.code.toUpperCase()}"
                scriptContent = assetText("scripts/ecs_verify_service_update.rb")
                    .replace("\$AWS_REGION_CODE", awsRegion.code)
                    .replace("\$AWS_REGION", awsRegion.region)
            }
        }
    }

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

        if (!ecsAccountId.matches("^[0-9]{12}$".toRegex())) {
            val message = "mandatory 'ecsAccountId' property must be a 12 digit numerical value"
            consumer.consumePropertyError("ecsAccountId", message)
        }

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

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

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

        updateRegions.forEach { awsRegion, data ->
            val count = data.desiredCount
            if (count == null) {
                val message = "mandatory 'desiredCount' property is not specified for ${awsRegion.code}"
                consumer.consumePropertyError("desiredCount", message)
            } else if (count < 0) {
                val message = "mandatory 'desiredCount' property is less than 0 for ${awsRegion.code}"
                consumer.consumePropertyError("desiredCount", message)
            }
        }
    }

    fun updateRegion(awsRegion: AWSRegion, taskDefinitionPath: String, desiredCount: Int? = null) {
        if (updateRegions.containsKey(awsRegion)) {
            throw DuplicateRegion("updateRegion", awsRegion.code)
        }

        updateRegions[awsRegion] = RegionalData(assetText(taskDefinitionPath), desiredCount)
    }
}

fun BuildGroup.updateECSService(buildName: String, init: UpdateECSService.() -> Unit): UpdateECSService =
    UpdateECSService(buildName, this, init)

fun InstrumProject.updateECSService(buildName: String, init: UpdateECSService.() -> Unit): UpdateECSService =
    BuildGroup(this, false).updateECSService(buildName, init)
