package twitch.instrumentorum

import jetbrains.buildServer.configs.kotlin.v2018_2.ArtifactDependency
import jetbrains.buildServer.configs.kotlin.v2018_2.BuildType
import jetbrains.buildServer.configs.kotlin.v2018_2.Dependency
import jetbrains.buildServer.configs.kotlin.v2018_2.FailureAction
import jetbrains.buildServer.configs.kotlin.v2018_2.Parameter
import jetbrains.buildServer.configs.kotlin.v2018_2.ParameterSpecFreeForm
import jetbrains.buildServer.configs.kotlin.v2018_2.SnapshotDependency
import jetbrains.buildServer.configs.kotlin.v2018_2.VcsRootEntry
import jetbrains.buildServer.configs.kotlin.v2018_2.buildFeatures.CommitStatusPublisher
import jetbrains.buildServer.configs.kotlin.v2018_2.triggers.FinishBuildTrigger
import jetbrains.buildServer.configs.kotlin.v2018_2.triggers.VcsTrigger
import jetbrains.buildServer.configs.kotlin.v2018_2.triggers.finishBuildTrigger
import jetbrains.buildServer.configs.kotlin.v2018_2.triggers.vcs
import jetbrains.buildServer.configs.kotlin.v2018_2.vcs.GitVcsRoot
import twitch.instrumentorum.enums.BranchFilter
import twitch.instrumentorum.exceptions.BuildNotFound
import twitch.instrumentorum.exceptions.ParameterNotFound
import twitch.instrumentorum.helpers.nameToId
import twitch.instrumentorum.project.build.features.githubStatusPublisher

@Suppress("LeakingThis", "TooManyFunctions")
open class InstrumBuild(name: String, val buildGroup: BuildGroup) : BuildType(), ParameterSupport {
    internal val githubStatusPublishers = arrayListOf<CommitStatusPublisher.() -> Unit>()
    internal val project = buildGroup.project
    internal val propagatedParams = mutableMapOf<String, Boolean>()
    internal val rootProject = project.rootProject

    internal val chainedFrom
        get() = dependencies.items.map(Dependency::buildTypeId).filterIsInstance<InstrumBuild>()

    init {
        buildGroup.builds.add(this)
        project.buildType(this)

        this.name = name
        id(nameToId(name, project))

        vcs.cleanCheckout = true
    }

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

    internal open fun finalize() {
        if (vcs.entries.count() == 0) {
            val root = rootProject.sourceVCSRoot
            vcs.entries.add(VcsRootEntry(root, listOf()))
        }

        val vcsRoot = vcs.entries.firstOrNull()?.root
        if (vcsRoot is GitVcsRoot) {
            githubStatusPublishers.forEach { githubStatusPublisher(vcsRoot, it) }
        }

        chainedFrom.forEach { build ->
            build.propagatedParams.forEach { paramName, readOnly ->
                if (!propagatedParams.containsKey(paramName)) {
                    propagateParams(paramName, readOnly = readOnly)
                }

                if (!params.hasParam(paramName)) {
                    paramFrom(build, paramName, readOnly = readOnly)
                }
            }
        }
    }

    internal fun finalizeBuildChain(chainFrom: ArrayList<InstrumBuild>) {
        if (dependencies.items.isNotEmpty()) {
            return
        }

        chainFrom.forEach { snapshotDependency(it) }
    }

    internal fun findBuild(buildName: String): InstrumBuild? =
        buildGroup.findBuild(buildName)

    internal fun findBuild(idNamespace: String, buildName: String): InstrumBuild? =
        buildGroup.findBuild(idNamespace, buildName)

    internal fun copyParam(param: Parameter, value: String, readOnly: Boolean = true) {
        val specType = param.spec?.type
        val specMap = param.spec?.toMap() ?: mutableMapOf()
        specMap["readOnly"] = readOnly.toString()

        val spec = if (specType != null) ParameterSpecFreeForm(specType, specMap) else null
        params.add(param.copy(value = value, spec = spec))
    }

    fun artifactDependency(build: BuildType, init: ArtifactDependency.() -> Unit = {}) {
        val snapshot = dependencies.items.filter { it.buildTypeId == build && it.snapshot is SnapshotDependency }

        if (snapshot.isEmpty()) {
            snapshotDependency(build)
        }

        dependencies.artifacts(build, init)
    }

    fun artifactDependency(idNamespace: String, buildName: String, init: ArtifactDependency.() -> Unit = {}) {
        val found = findBuild(idNamespace, buildName)
            ?: throw BuildNotFound("Unable to find build named `$buildName`")

        artifactDependency(found, init)
    }

    fun artifactDependency(buildName: String, init: ArtifactDependency.() -> Unit = {}) =
        artifactDependency("", buildName, init)

    fun githubStatusPublisher(init: CommitStatusPublisher.() -> Unit = {}) =
        githubStatusPublishers.add(init)

    fun paramFrom(build: InstrumBuild, paramName: String, newValue: String? = null, readOnly: Boolean = true): String {
        val param = build.params.findRawParam(paramName)
            ?: throw ParameterNotFound("Unable to find parameter named `$paramName` on `$build`")

        copyParam(param, newValue ?: valueFrom(build, paramName), readOnly)

        return "%$paramName%"
    }

    fun paramFrom(
        idNamespace: String,
        buildName: String,
        paramName: String,
        newValue: String? = null,
        readOnly: Boolean = true
    ): String {
        val found = findBuild(idNamespace, buildName)
            ?: throw BuildNotFound("Unable to find build named `$buildName`")

        return paramFrom(found, paramName, newValue, readOnly)
    }

    fun paramFrom(buildName: String, paramName: String, newValue: String? = null, readOnly: Boolean = true): String =
        paramFrom("", buildName, paramName, newValue, readOnly)

    fun propagateParams(vararg params: String, readOnly: Boolean = true) =
        params.forEach { propagatedParams[it] = readOnly }

    fun snapshotDependency(build: BuildType, init: SnapshotDependency.() -> Unit = {}) {
        dependencies.snapshot(build) {
            onDependencyCancel = FailureAction.CANCEL
            onDependencyFailure = FailureAction.CANCEL

            init()
        }

        if (buildNumberPattern.isBlank() || buildNumberPattern == "%build.counter%") {
            buildNumberPattern = build.depParamRefs.buildNumber.ref
        }
    }

    fun snapshotDependency(idNamespace: String, buildName: String, init: SnapshotDependency.() -> Unit = {}) {
        val found = findBuild(idNamespace, buildName)
            ?: throw BuildNotFound("Unable to find build named `$buildName`")

        snapshotDependency(found, init)
    }

    fun snapshotDependency(buildName: String, init: SnapshotDependency.() -> Unit = {}) =
        snapshotDependency("", buildName, init)

    fun valueFrom(build: BuildType, paramName: String) =
        build.depParamRefs[paramName].ref

    fun valueFrom(idNamespace: String, buildName: String, paramName: String): String {
        val found = buildGroup.findBuild(idNamespace, buildName)
            ?: throw BuildNotFound("Unable to find build named `$buildName`")

        return valueFrom(found, paramName)
    }

    fun valueFrom(buildName: String, paramName: String): String =
        valueFrom("", buildName, paramName)

    fun finishBuildTrigger(build: BuildType, onlyOnSuccess: Boolean = true, init: FinishBuildTrigger.() -> Unit = {}) {
        triggers.finishBuildTrigger {
            buildType = build.id.toString()
            successfulOnly = onlyOnSuccess
            init()
        }
    }

    fun finishBuildTrigger(idNamespace: String, buildName: String, onlyOnSuccess: Boolean = true, init: FinishBuildTrigger.() -> Unit = {}) {
        val found = buildGroup.findBuild(idNamespace, buildName)
            ?: throw BuildNotFound("Unable to find build named `$buildName`")

        finishBuildTrigger(found, onlyOnSuccess, init)
    }

    fun finishBuildTrigger(buildName: String, onlyOnSuccess: Boolean = true, init: FinishBuildTrigger.() -> Unit = {}) =
        finishBuildTrigger("", buildName, onlyOnSuccess, init)

    fun vcsTrigger(branchFilter: String? = null, init: VcsTrigger.() -> Unit = {}) {
        triggers.vcs {
            this.branchFilter = branchFilter
            init()
        }
    }

    fun vcsTrigger(branchFilter: BranchFilter, init: VcsTrigger.() -> Unit = {}) =
        vcsTrigger(branchFilter.filter, init)
}
