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.enums.ECSLaunchType
import twitch.instrumentorum.exceptions.DuplicateRegion
import twitch.instrumentorum.helpers.assetText

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

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

    var deployRole = ""
    var desiredCount = 1
    var dockerImage = "%ecr.domain%/%ecr.repo%:%git.commit.short%"
    var ecsAccountId = ""
    var ecsCluster = ""
    var ecsLaunchType = ECSLaunchType.EC2
    var ecsTask = ""
    var ecsTaskFamily = "%ecs.task%"
    var ecsTaskSchedule = ""

    init {
        type = Type.DEPLOYMENT
    }

    constructor(name: String, buildGroup: BuildGroup, init: UpdateECSScheduledTask.() -> 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.launch.type", ecsLaunchType.key, "ECS Launch Type")
        textParam("ecs.task", ecsTask, "Target ECS Scheduled Task")
        textParam("ecs.task.family", ecsTaskFamily, "ECS Task Family")
        textParam("ecs.task.schedule", ecsTaskSchedule, "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 Scheduled Task updates
        updateRegions.forEach { awsRegion, data ->
            steps.script {
                name = "Update ECS Scheduled Task in ${awsRegion.code.toUpperCase()}"
                scriptContent = assetText("scripts/ecs_update_scheduled_task.rb")
                    .replace("\$DESIRED_COUNT", data.desiredCount.toString())
                    .replace("\$TASK_EXECUTOR_ROLE", data.executorRole)
                    .replace("\$ECS_NETWORK_CONFIGURATION", data.networkConfiguration)
                    .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 (ecsTask.isBlank()) {
            consumer.consumePropertyError("ecsTask", "mandatory 'ecsTask' property is not specified")
        }

        if (ecsTaskSchedule.isBlank()) {
            consumer.consumePropertyError("ecsTaskSchedule", "mandatory 'ecsTaskSchedule' 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)
            }

            if (data.networkConfiguration.isNotBlank() && ecsLaunchType == ECSLaunchType.EC2) {
                val message = "property 'ecsLaunchType' must be set to FARGATE if a networkConfiguration is provided"
                consumer.consumePropertyError("ecsLaunchType", message)
            }

            if (data.networkConfiguration.isBlank() && ecsLaunchType == ECSLaunchType.FARGATE) {
                val message = "when property 'ecsLaunchType' is set to FARGATE you must provide 'networkConfigurationPath' for each region"
                consumer.consumePropertyError("ecsLaunchType", message)
            }
        }
    }

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

        val networkConfiguration = if (networkConfigurationPath == null) "" else assetText(networkConfigurationPath)
        updateRegions[awsRegion] = RegionalData(
            assetText(taskDefinitionPath),
            networkConfiguration,
            executorRole,
            desiredCount
        )
    }
}

fun BuildGroup.updateECSScheduledTask(buildName: String, init: UpdateECSScheduledTask.() -> Unit): UpdateECSScheduledTask =
    UpdateECSScheduledTask(buildName, this, init)

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