import com.github.spotbugs.snom.Confidence
import com.github.spotbugs.snom.Effort
import com.github.spotbugs.snom.SpotBugsTask
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion

plugins {
    id 'base'
    id 'com.github.spotbugs' version '6.0.7'
    id 'org.jetbrains.kotlin.jvm' version '2.0.0'
    id 'com.google.devtools.ksp' version '2.0.0-1.0.24'
}

allprojects {
    apply plugin: 'base'
    apply plugin: 'java'
    apply plugin: 'java-test-fixtures'
    apply plugin: 'com.github.spotbugs'
    apply plugin: 'org.jetbrains.kotlin.jvm'
    apply plugin: 'com.google.devtools.ksp'
    apply plugin: 'idea'
    apply plugin: 'project-report'


    // By default gradle uses directory as the project name. That works very well in a single project environment but
    // projects clobber each other in an environments with subprojects when projects are in directories named identically.
    def sub = rootDir.relativePath(projectDir.parentFile).replace('/', '.')
    group = "io.${rootProject.name}${sub.isEmpty() ? '' : ".$sub"}"
    project.base.archivesName = "${project.group}-${project.name}"

    // Produce reproducible archives
    // (see https://docs.gradle.org/current/userguide/working_with_files.html#sec:reproducible_archives)
    tasks.withType(AbstractArchiveTask).configureEach {
        preserveFileTimestamps = false
        reproducibleFileOrder = true
    }

    // Common configurations for 'assemble'.
    tasks.withType(Tar).configureEach {
        duplicatesStrategy DuplicatesStrategy.INCLUDE
    }
    tasks.withType(Zip).configureEach {
        duplicatesStrategy DuplicatesStrategy.INCLUDE
        // Disabling distZip causes the build to break for some reason, so: instead of disabling it, make it fast.
        entryCompression ZipEntryCompression.STORED
    }

    // Convenience task to list all dependencies per project
    tasks.register('listAllDependencies', DependencyReportTask) {}

    // Common java configurations

    java {
        withSourcesJar()
        withJavadocJar()
        sourceCompatibility = JavaVersion.VERSION_21
        targetCompatibility = JavaVersion.VERSION_21
        compileJava {
            options.compilerArgs += ["-Xlint:all,-serial,-processing"] + getJavaCompilerArgs()
        }
        compileTestJava {
            //rawtypes and unchecked are necessary for mockito
            //deprecation and removal are removed from error since we should still test those constructs.
            options.compilerArgs += ["-Xlint:all,-serial,-processing,-rawtypes,-unchecked,-deprecation,-removal"] + getJavaCompilerArgs()
        }
        compileTestFixturesJava {
            //rawtypes and unchecked are necessary for mockito
            options.compilerArgs += ["-Xlint:all,-serial,-processing,-rawtypes,-unchecked"] + getJavaCompilerArgs()
        }
    }

    tasks.named('sourcesJar').configure {
        dependsOn tasks.matching { it.name == 'generate' }
    }

    compileKotlin {
        compilerOptions {
            jvmTarget = JvmTarget.JVM_21
            languageVersion = KotlinVersion.KOTLIN_1_9
            allWarningsAsErrors = isInCi()
            freeCompilerArgs = ["-Xjvm-default=all"] + getKotlinCompilerArgs()
        }
        dependsOn {
            tasks.matching { it.name == 'generate' }
        }
    }
    compileTestKotlin {
        compilerOptions {
            jvmTarget = JvmTarget.JVM_21
            languageVersion = KotlinVersion.KOTLIN_1_9
            allWarningsAsErrors = isInCi()
            freeCompilerArgs = ["-Xjvm-default=all"] + getKotlinCompilerArgs()
        }
        dependsOn {
            tasks.matching { it.name == 'generate' }
        }
    }
    compileTestFixturesKotlin {
        compilerOptions {
            jvmTarget = JvmTarget.JVM_21
            languageVersion = KotlinVersion.KOTLIN_1_9
            allWarningsAsErrors = isInCi()
            freeCompilerArgs = ["-Xjvm-default=all"] + getKotlinCompilerArgs()
        }
        dependsOn {
            tasks.matching { it.name == 'generate' }
        }
    }

    configurations.all {
        resolutionStrategy {
            preferProjectModules()
        }
    }

    idea {
        module {
            downloadJavadoc = true
            downloadSources = true
        }
    }

    spotbugs {
        ignoreFailures = false
        effort = Effort.valueOf('MAX')
        excludeFilter.set rootProject.file('spotbugs-exclude-filter-file.xml')
        reportLevel = Confidence.valueOf('HIGH')
        showProgress = false
        toolVersion = '4.8.3'
    }

    test {
        useJUnitPlatform()
        testLogging() {
            events 'skipped', 'started', 'passed', 'failed'
            exceptionFormat 'full'
            showStandardStreams = true
        }
        reports {
            junitXml {
                outputPerTestCase = true
            }
        }

        // This is required by mockito, see https://github.com/mockito/mockito/issues/3037.
        jvmArgs "-XX:+EnableDynamicAgentLoading"
        // This is also required, to prevent stderr spam starting with
        //   "OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader cl..."
        jvmArgs "-Xshare:off"
        // needed because we make ThreadLocal.get(Thread) accessible in IntegrationRunner.stopOrphanedThreads
        jvmArgs "--add-opens=java.base/java.lang=ALL-UNNAMED"

        // Set the timezone to UTC instead of picking up the host machine's timezone,
        // which on a developer's laptop is more likely to be PST.
        systemProperty 'user.timezone', 'UTC'

        // Enable parallel test execution in JUnit by default.
        // This is to support @Execution(ExecutionMode.CONCURRENT) annotations
        // See https://junit.org/junit5/docs/current/user-guide/#writing-tests-parallel-execution for details.
        systemProperty 'junit.jupiter.execution.parallel.enabled', 'true'
        // Concurrency takes place at the class level.
        systemProperty 'junit.jupiter.execution.parallel.mode.classes.default', 'concurrent'
        // Within a class, the test methods are still run serially on the same thread.
        systemProperty 'junit.jupiter.execution.parallel.mode.default', 'same_thread'
        // Effectively disable JUnit concurrency by running tests in only one thread by default.
        systemProperty 'junit.jupiter.execution.parallel.config.strategy', 'fixed'
        systemProperty 'junit.jupiter.execution.parallel.config.fixed.parallelism', 1
        // Order test classes by annotation.
        systemProperty 'junit.jupiter.testclass.order.default', 'org.junit.jupiter.api.ClassOrderer$OrderAnnotation'
        systemProperty 'junit.jupiter.extensions.autodetection.enabled', 'true'

        if (!project.hasProperty('testExecutionConcurrency')) {
            // By default, let gradle spawn as many independent workers as it wants.
            maxParallelForks = Runtime.runtime.availableProcessors()
            maxHeapSize = '3G'
        } else {
            // Otherwise, run tests within the same JVM.
            // Let gradle spawn only one worker.
            maxParallelForks = 1
            maxHeapSize = '8G'
            // Manage test execution concurrency in JUnit.
            String concurrency = project.property('testExecutionConcurrency').toString()
            if (concurrency.isInteger() && (concurrency as int) > 0) {
                // Define a fixed number of threads when the property is set to a positive integer.
                systemProperty 'junit.jupiter.execution.parallel.config.fixed.parallelism', concurrency
            } else {
                // Otherwise let JUnit manage the concurrency dynamically.
                systemProperty 'junit.jupiter.execution.parallel.config.strategy', 'dynamic'
            }
        }
        String junitMethodExecutionTimeout
        if (project.hasProperty('JunitMethodExecutionTimeout')) {
            junitMethodExecutionTimeout = project.property('JunitMethodExecutionTimeout').toString()
        } else {
            junitMethodExecutionTimeout = '1 m'
        }

        systemProperty 'junit.jupiter.execution.timeout.default', junitMethodExecutionTimeout
        // This is used by the "old" cdk (i.e. airbyte-cdk/java)
        systemProperty 'JunitMethodExecutionTimeout', junitMethodExecutionTimeout
    }

    dependencies {
        // JUnit dependencies.
        def vAssertJ = "3.25.3"
        def vJUnit = "5.10.2"
        def vJUnitJupiter = "5.11.0"

        testFixturesImplementation platform("org.junit:junit-bom:${vJUnit}")
        testFixturesImplementation "org.junit.jupiter:junit-jupiter-api:${vJUnit}"
        testFixturesImplementation "org.junit.jupiter:junit-jupiter-params:${vJUnit}"
        testFixturesImplementation "org.mockito:mockito-junit-jupiter:${vJUnitJupiter}"
        testFixturesImplementation "org.assertj:assertj-core:${vAssertJ}"

        testImplementation platform("org.junit:junit-bom:${vJUnit}")
        testImplementation "org.junit.jupiter:junit-jupiter-api:${vJUnit}"
        testImplementation "org.junit.jupiter:junit-jupiter-params:${vJUnit}"
        testImplementation "org.mockito:mockito-junit-jupiter:${vJUnitJupiter}"
        testImplementation "org.assertj:assertj-core:${vAssertJ}"
        testImplementation 'org.mockito.kotlin:mockito-kotlin:5.2.1'

        testRuntimeOnly platform("org.junit:junit-bom:${vJUnit}")
        testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${vJUnit}"

        // Spotbugs dependencies.
        def vSpotbugs = "4.8.3"
        implementation "com.github.spotbugs:spotbugs-annotations:${vSpotbugs}"
    }

    tasks.withType(SpotBugsTask).configureEach {
        // Reports can be found under each subproject in build/spotbugs/
        reports {
            xml.required = false
            html.required = true
        }
    }

    javadoc {
        options.addStringOption('Xdoclint:none', '-quiet')
    }
}

static def isInCi() {
    return System.getenv("CI") == "true"
}

static def getJavaCompilerArgs() {
    if (isInCi()) {
        return ["-Werror"]
    } else {
        return []
    }
}

static def getKotlinCompilerArgs() {
    if (isInCi()) {
        return []
    } else {
        return ["-Xdebug"]
    }
}
