import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.specs.Spec
import org.gradle.api.tasks.testing.Test
import org.gradle.jvm.tasks.Jar
import org.gradle.language.jvm.tasks.ProcessResources
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

private val prereleaseMarkers =
    listOf("-alpha", "-beta", "-rc", "-cr", "-m", "-preview", "-eap", "snapshot")
private val dependencyUpdateConfigurations =
    setOf("compileClasspath", "testCompileClasspath", "testRuntimeClasspath", "downloadLauncherRuntime")
private val excludedShadowMetadata =
    arrayOf(
        "META-INF/INDEX.LIST",
        "META-INF/*.SF",
        "META-INF/*.DSA",
        "META-INF/*.RSA",
        "META-INF/versions/**/module-info.class",
        "module-info.class",
    )

private fun isPrerelease(version: String): Boolean = prereleaseMarkers.any { marker -> version.contains(marker, ignoreCase = true) }

private fun ShadowJar.requireUniqueEntries() {
    duplicatesStrategy = DuplicatesStrategy.INCLUDE
    failOnDuplicateEntries = true
    exclude(*excludedShadowMetadata)
}

plugins {
    kotlin("jvm") version "2.4.10"
    kotlin("plugin.serialization") version "2.4.10"
    id("com.gradleup.shadow") version "9.6.1"
    id("org.jlleitschuh.gradle.ktlint") version "14.2.0"
}

group = "com.seaoflaurels.minecraft"
version = "1.0.0"

repositories {
    mavenCentral()
}

tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
    revision = "release"
    gradleReleaseChannel = "current"
    outputFormatter = "json,plain"
    filterConfigurations =
        Spec<Configuration> { configuration ->
            configuration.name in dependencyUpdateConfigurations
        }
    rejectVersionIf {
        isPrerelease(candidate.version)
    }
}

val downloadLauncherRuntime =
    configurations.create("downloadLauncherRuntime") {
        isCanBeConsumed = false
        isCanBeResolved = true
        extendsFrom(configurations.implementation.get(), configurations.runtimeOnly.get())
    }

dependencies {
    compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")
    testImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")
    downloadLauncherRuntime("org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0")
    testImplementation(platform("org.junit:junit-bom:6.1.3"))
    testImplementation(kotlin("test-junit5"))
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

val upstreamVersionsFile = layout.projectDirectory.file("../upstream-versions.json")

kotlin {
    jvmToolchain(25)
    compilerOptions {
        jvmTarget.set(JvmTarget.JVM_25)
        allWarningsAsErrors.set(true)
    }
}

tasks.named<ProcessResources>("processResources") {
    from(upstreamVersionsFile)
}

val fatJarRuntime =
    configurations.create("fatJarRuntime") {
        isCanBeConsumed = false
        isCanBeResolved = true
        extendsFrom(configurations.implementation.get(), configurations.runtimeOnly.get())
    }

fun registerWrapperJar(
    taskName: String,
    filename: String,
    mainClass: String,
    launcherClasses: List<String>,
    includeUpstreamVersions: Boolean = false,
) = tasks.register<ShadowJar>(taskName) {
    val compileKotlin = tasks.named<KotlinCompile>("compileKotlin")
    dependsOn(compileKotlin)
    archiveFileName.set(filename)
    destinationDirectory.set(layout.buildDirectory.dir("libs"))
    requireUniqueEntries()
    configurations =
        project.provider {
            if (includeUpstreamVersions) {
                listOf(downloadLauncherRuntime)
            } else {
                listOf(fatJarRuntime)
            }
        }
    manifest.attributes(
        "Main-Class" to mainClass,
        "Enable-Native-Access" to "ALL-UNNAMED",
    )
    from(compileKotlin.flatMap { it.destinationDirectory }) {
        include("launchers/NativeExec*")
        include("launchers/NativeKt*")
        launcherClasses.forEach(::include)
    }
    if (includeUpstreamVersions) {
        from(upstreamVersionsFile)
    }
}

val minecraftJavaServerJar =
    registerWrapperJar(
        "minecraftJavaServerJar",
        "minecraft-java-server-wrapper.jar",
        "launchers.MinecraftJavaServerMain",
        listOf(
            "launchers/DownloadLauncher*",
            "launchers/LockedArtifact*",
            "launchers/UpstreamVersions*",
            "launchers/Versions*",
            "launchers/MinecraftJavaServerMain*",
        ),
        includeUpstreamVersions = true,
    )
val minecraftServerJavaJar =
    registerWrapperJar(
        "minecraftServerJavaJar",
        "minecraft-server-java.jar",
        "launchers.MinecraftServerJavaMain",
        listOf("launchers/SupervisorLauncher*", "launchers/MinecraftServerJavaMain*"),
    )
val minecraftServerFabricJar =
    registerWrapperJar(
        "minecraftServerFabricJar",
        "minecraft-server-fabric.jar",
        "launchers.MinecraftServerFabricMain",
        listOf("launchers/SupervisorLauncher*", "launchers/MinecraftServerFabricMain*"),
    )
val minecraftFabricServerJar =
    registerWrapperJar(
        "minecraftFabricServerJar",
        "minecraft-fabric-server-wrapper.jar",
        "launchers.MinecraftFabricServerMain",
        listOf(
            "launchers/DownloadLauncher*",
            "launchers/LockedArtifact*",
            "launchers/UpstreamVersions*",
            "launchers/Versions*",
            "launchers/MinecraftFabricServerMain*",
        ),
        includeUpstreamVersions = true,
    )
val geyserStandaloneJar =
    registerWrapperJar(
        "geyserStandaloneJar",
        "geyser-standalone-wrapper.jar",
        "launchers.GeyserStandaloneMain",
        listOf(
            "launchers/DownloadLauncher*",
            "launchers/LockedArtifact*",
            "launchers/UpstreamVersions*",
            "launchers/Versions*",
            "launchers/GeyserStandaloneMain*",
        ),
        includeUpstreamVersions = true,
    )
val mcXboxBroadcastStandaloneJar =
    registerWrapperJar(
        "mcXboxBroadcastStandaloneJar",
        "mcxboxbroadcast-standalone-wrapper.jar",
        "launchers.McXboxBroadcastStandaloneMain",
        listOf(
            "launchers/DownloadLauncher*",
            "launchers/LockedArtifact*",
            "launchers/UpstreamVersions*",
            "launchers/Versions*",
            "launchers/McXboxBroadcastStandaloneMain*",
        ),
        includeUpstreamVersions = true,
    )

tasks.named<Jar>("jar") {
    enabled = false
}

tasks.named<ShadowJar>("shadowJar") {
    enabled = false
}

val repositoryDirectory = layout.projectDirectory.dir("..")
val nativeExecLibrary =
    repositoryDirectory.file("target/release/libjar_wrapper_exec.so")
val buildNativeExec =
    tasks.register<Exec>("buildNativeExec") {
        workingDir(repositoryDirectory)
        commandLine("cargo", "build", "--locked", "--release", "--package", "jar-wrapper-exec")
        inputs.files(
            repositoryDirectory.file("Cargo.toml"),
            repositoryDirectory.file("Cargo.lock"),
            layout.projectDirectory.file("native-exec/Cargo.toml"),
            fileTree(layout.projectDirectory.dir("native-exec/src")),
        )
        outputs.file(nativeExecLibrary)
    }

val nativeExecTestJar =
    tasks.register<ShadowJar>("nativeExecTestJar") {
        val compileKotlin = tasks.named<KotlinCompile>("compileKotlin")
        val compileTestKotlin = tasks.named<KotlinCompile>("compileTestKotlin")
        dependsOn(compileKotlin, compileTestKotlin)
        archiveFileName.set("native-exec-test.jar")
        destinationDirectory.set(layout.buildDirectory.dir("test-libs"))
        requireUniqueEntries()
        configurations = project.provider { listOf(fatJarRuntime) }
        manifest.attributes(
            "Main-Class" to "launchers.NativeExecTestMain",
            "Enable-Native-Access" to "ALL-UNNAMED",
        )
        from(compileKotlin.flatMap { it.destinationDirectory }) {
            include("launchers/NativeExec*")
            include("launchers/NativeKt*")
        }
        from(compileTestKotlin.flatMap { it.destinationDirectory }) {
            include("launchers/NativeExecTestMain*")
        }
    }

val resolveTestRuntimeDependencies =
    tasks.register("resolveTestRuntimeDependencies") {
        inputs.files(configurations.testRuntimeClasspath)
        doLast {
            configurations.testRuntimeClasspath.get().files
        }
    }

tasks.named<Test>("test") {
    useJUnitPlatform()
    dependsOn(
        buildNativeExec,
        minecraftJavaServerJar,
        minecraftFabricServerJar,
        minecraftServerJavaJar,
        minecraftServerFabricJar,
        geyserStandaloneJar,
        mcXboxBroadcastStandaloneJar,
        nativeExecTestJar,
    )
    inputs.files(
        minecraftJavaServerJar.flatMap { it.archiveFile },
        minecraftFabricServerJar.flatMap { it.archiveFile },
        minecraftServerJavaJar.flatMap { it.archiveFile },
        minecraftServerFabricJar.flatMap { it.archiveFile },
        geyserStandaloneJar.flatMap { it.archiveFile },
        mcXboxBroadcastStandaloneJar.flatMap { it.archiveFile },
        nativeExecLibrary,
        nativeExecTestJar.flatMap { it.archiveFile },
    )
    systemProperty("launcherProjectDirectory", layout.projectDirectory.asFile.absolutePath)
    systemProperty("nativeExecLibrary", nativeExecLibrary.asFile.absolutePath)
    val nativeExecTestJarPath =
        nativeExecTestJar
            .flatMap { it.archiveFile }
            .get()
            .asFile
            .absolutePath
    systemProperty("nativeExecTestJar", nativeExecTestJarPath)
}

tasks.assemble {
    dependsOn(
        minecraftJavaServerJar,
        minecraftFabricServerJar,
        minecraftServerJavaJar,
        minecraftServerFabricJar,
        geyserStandaloneJar,
        mcXboxBroadcastStandaloneJar,
    )
}
