import org.jetbrains.compose.gradle.standardConf

plugins {
    kotlin("multiplatform")
    id("org.jetbrains.compose")
}


kotlin {
    js(IR) {
        browser() {
            testTask {
                useKarma {
                    standardConf()
                }
            }
        }
        binaries.executable()
    }

    sourceSets {
        val jsMain by getting {
            dependencies {
                implementation(project(":compose-compiler-integration-lib"))
                implementation(kotlin("stdlib-js"))
                implementation(compose.runtime)
                implementation(project(":html-core"))
            }
        }

        val jsTest by getting {
            dependencies {
                implementation(kotlin("test-js"))
            }
        }
    }
}


fun cloneTemplate(templateName: String, contentMain: String, contentLib: String): File {
    val tempDir = file("${project.buildDir.absolutePath}/temp/cloned-$templateName")
    tempDir.deleteRecursively()
    tempDir.mkdirs()
    file("${projectDir.absolutePath}/main-template").copyRecursively(tempDir)
    // tempDir.deleteOnExit()
    File("$tempDir/src/commonMain/kotlin/Main.kt").printWriter().use { out ->
        out.println(contentMain)
    }
    File("$tempDir/lib/src/commonMain/kotlin/Lib.kt").printWriter().use { out ->
        out.println(contentLib)
    }
    return tempDir
}

fun build(
    caseName: String,
    directory: File,
    failureExpected: Boolean = false,
    composeVersion: String,
    kotlinVersion: String,
    vararg buildCmd: String = arrayOf("build", "jsNodeRun")
) {
    val isWin = System.getProperty("os.name").startsWith("Win")
    val arguments = buildCmd.toMutableList().also {
        it.add("-Pcompose.version=$composeVersion")
        it.add("-Pkotlin.version=$kotlinVersion")
    }.toTypedArray()

    val procBuilder = if (isWin) {
        ProcessBuilder("gradlew.bat", *arguments)
    } else {
        ProcessBuilder("bash", "./gradlew", *arguments)
    }
    val proc = procBuilder
        .directory(directory)
        .redirectOutput(ProcessBuilder.Redirect.PIPE)
        .redirectError(ProcessBuilder.Redirect.PIPE)
        .start()

    proc.waitFor(5, TimeUnit.MINUTES)

    "(COMPOSE_INTEGRATION_VERSION=\\[.*\\])".toRegex().find(
        proc.inputStream.bufferedReader().readText()
    )?.also {
        println(it.groupValues[1])
    }

    println(proc.errorStream.bufferedReader().readText())

    if (proc.exitValue() != 0 && !failureExpected) {
        throw GradleException("Error compiling $caseName")
    }

    if (failureExpected && proc.exitValue() == 0) {
        throw AssertionError("$caseName compilation did not fail!!!")
    }
}

data class RunChecksResult(
    val cases: Map<String, Throwable?>
) {
    val totalCount = cases.size
    val failedCount = cases.filter { it.value != null }.size
    val hasFailed = failedCount > 0

    fun printResults() {
        cases.forEach { (name, throwable) ->
            println(name + " : " + (throwable ?: "OK"))
        }
    }

    fun reportToTeamCity() {
        cases.forEach { (caseName, error) ->
            println("##teamcity[testStarted name='compileTestCase_$caseName']")
            if (error != null) {
                println("##teamcity[testFailed name='compileTestCase_$caseName']")
            }
            println("##teamcity[testFinished name='compileTestCase_$caseName']")
        }
    }
}

fun runCasesInDirectory(
    dir: File,
    filterPath: String,
    expectCompilationError: Boolean,
    composeVersion: String,
    kotlinVersion: String
): RunChecksResult {
    return dir.listFiles()!!.filter { it.absolutePath.contains(filterPath) }.mapIndexed { _, file ->
        println("Running check for ${file.name}, expectCompilationError = $expectCompilationError, composeVersion = $composeVersion")

        val contentLines = file.readLines()
        val startMainLineIx = contentLines.indexOf("// @Module:Main").let { ix ->
            if (ix == -1) 0 else ix + 1
        }

        val startLibLineIx = contentLines.indexOf("// @Module:Lib").let { ix ->
            if (ix == -1) contentLines.size else ix - 1
        }

        require(startMainLineIx < startLibLineIx) {
            "The convention is that @Module:Lib should go after @Module:Main"
        }

        val mainContent = contentLines.let { lines ->
            val endLineIx = if (startLibLineIx < lines.size) startLibLineIx - 1 else lines.lastIndex
            lines.slice(startMainLineIx..endLineIx).joinToString(separator = "\n")
        }

        val libContent = contentLines.let { lines ->
            if (startLibLineIx < lines.size) {
                lines.slice(startLibLineIx..lines.lastIndex)
            } else {
                emptyList()
            }.joinToString(separator = "\n")
        }

        val caseName = file.name
        val tmpDir = cloneTemplate(caseName, contentMain = mainContent, contentLib = libContent)

        caseName to kotlin.runCatching {
            build(
                caseName = caseName,
                directory = tmpDir,
                failureExpected = expectCompilationError,
                composeVersion = composeVersion,
                kotlinVersion = kotlinVersion
            )
        }.exceptionOrNull()

    }.let {
        RunChecksResult(it.toMap())
    }
}

tasks.register("checkComposeCases") {
    doLast {
        val filterCases = project.findProperty("FILTER_CASES")?.toString() ?: ""
        val composeVersion = project.findProperty("compose.version")?.toString() ?: "0.0.0-SNASPHOT"
        val kotlinVersion = kotlin.coreLibrariesVersion

        val expectedFailingCasesDir = File("${projectDir.absolutePath}/testcases/failing")
        val expectedFailingResult = runCasesInDirectory(
            dir = expectedFailingCasesDir,
            expectCompilationError = true,
            filterPath = filterCases,
            composeVersion = composeVersion,
            kotlinVersion = kotlinVersion
        )

        val passingCasesDir = File("${projectDir.absolutePath}/testcases/passing")
        val passingResult = runCasesInDirectory(
            dir = passingCasesDir,
            expectCompilationError = false,
            filterPath = filterCases,
            composeVersion = composeVersion,
            kotlinVersion = kotlinVersion
        )

        expectedFailingResult.printResults()
        expectedFailingResult.reportToTeamCity()

        passingResult.printResults()
        passingResult.reportToTeamCity()

        if (expectedFailingResult.hasFailed || passingResult.hasFailed) {
            error("There were failed cases. Check the logs above")
        }
    }
}
