/*
 * Nextcloud - Android Client
 *
 * SPDX-FileCopyrightText: 2025 Jimly Asshiddiqy <jimly.asshiddiqy@accenture.com>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */
@file:Suppress("UnstableApiUsage", "DEPRECATION")

import com.android.build.gradle.internal.api.ApkVariantOutputImpl
import com.github.spotbugs.snom.Confidence
import com.github.spotbugs.snom.Effort
import com.github.spotbugs.snom.SpotBugsTask
import com.karumi.shot.ShotExtension
import org.gradle.internal.jvm.Jvm
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.io.FileInputStream
import java.util.Properties

val shotTest = System.getenv("SHOT_TEST") == "true"
val ciBuild = System.getenv("CI") == "true"
val perfAnalysis = project.hasProperty("perfAnalysis")

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.compose)
    alias(libs.plugins.spotless)
    alias(libs.plugins.kapt)
    alias(libs.plugins.ksp)
    alias(libs.plugins.kotlin.serialization)
    alias(libs.plugins.kotlin.parcelize)
    alias(libs.plugins.jetbrains.kotlin.android)
    alias(libs.plugins.spotbugs)
    alias(libs.plugins.detekt)
    // needed to make renovate run without shot, as shot requires Android SDK
    // https://github.com/pedrovgs/Shot/issues/300
    if (System.getenv("SHOT_TEST") == "true") alias(libs.plugins.shot)
    id("checkstyle")
    id("pmd")
}
apply(from = "${rootProject.projectDir}/jacoco.gradle.kts")

println("Gradle uses Java ${Jvm.current()}")

configurations.configureEach {
    // via prism4j, already using annotations explicitly
    exclude(group = "org.jetbrains", module = "annotations-java5")

    resolutionStrategy {
        force(libs.objenesis)

        eachDependency {
            if (requested.group == "org.checkerframework" && requested.name != "checker-compat-qual") {
                useVersion(libs.versions.checker.get())
                because("https://github.com/google/ExoPlayer/issues/10007")
            } else if (requested.group == "org.jacoco") {
                useVersion(libs.versions.jacoco.get())
            } else if (requested.group == "commons-logging" && requested.name == "commons-logging") {
                useTarget(libs.slfj)
            }
        }
    }
}

// semantic versioning for version code
val versionMajor = 3
val versionMinor = 35
val versionPatch = 0
val versionBuild = 0 // 0-50=Alpha / 51-98=RC / 90-99=stable

val ndkEnv = buildMap {
    file("${project.rootDir}/ndk.env").readLines().forEach {
        val (key, value) = it.split("=")
        put(key, value)
    }
}

val configProps = Properties().apply {
    val file = rootProject.file(".gradle/config.properties")
    if (file.exists()) load(FileInputStream(file))
}

val ncTestServerUsername = configProps["NC_TEST_SERVER_USERNAME"]
val ncTestServerPassword = configProps["NC_TEST_SERVER_PASSWORD"]
val ncTestServerBaseUrl = configProps["NC_TEST_SERVER_BASEURL"]

android {
    // install this NDK version and Cmake to produce smaller APKs. Build will still work if not installed
    ndkVersion = "${ndkEnv["NDK_VERSION"]}"

    namespace = "com.owncloud.android"
    testNamespace = "${namespace}.test"

    androidResources.generateLocaleConfig = true

    defaultConfig {
        applicationId = "com.nextcloud.client"
        minSdk = 27
        targetSdk = 36
        compileSdk = 36

        buildConfigField("boolean", "CI", ciBuild.toString())
        buildConfigField("boolean", "RUNTIME_PERF_ANALYSIS", perfAnalysis.toString())

        javaCompileOptions.annotationProcessorOptions {
            arguments += mapOf("room.schemaLocation" to "$projectDir/schemas")
        }

        // arguments to be passed to functional tests
        testInstrumentationRunner = if (shotTest) "com.karumi.shot.ShotTestRunner"
        else "com.nextcloud.client.TestRunner"

        testInstrumentationRunnerArguments += mapOf(
            "TEST_SERVER_URL" to ncTestServerBaseUrl.toString(),
            "TEST_SERVER_USERNAME" to ncTestServerUsername.toString(),
            "TEST_SERVER_PASSWORD" to ncTestServerPassword.toString()
        )
        testInstrumentationRunnerArguments["disableAnalytics"] = "true"

        versionCode = versionMajor * 10000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
        versionName = when {
            versionBuild > 89 -> "${versionMajor}.${versionMinor}.${versionPatch}"
            versionBuild > 50 -> "${versionMajor}.${versionMinor}.${versionPatch} RC" + (versionBuild - 50)
            else -> "${versionMajor}.${versionMinor}.${versionPatch} Alpha" + (versionBuild + 1)
        }

        // adapt structure from Eclipse to Gradle/Android Studio expectations;
        // see http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Configuring-the-Structure

        flavorDimensions += "default"

        buildTypes {
            release {
                buildConfigField("String", "NC_TEST_SERVER_DATA_STRING", "\"\"")
            }

            debug {
                enableUnitTestCoverage = project.hasProperty("coverage")
                resConfigs("xxxhdpi")

                buildConfigField(
                    "String",
                    "NC_TEST_SERVER_DATA_STRING",
                    "\"nc://login/user:${ncTestServerUsername}&password:${ncTestServerPassword}&server:${ncTestServerBaseUrl}\""
                )
            }
        }

        productFlavors {
            // used for f-droid
            register("generic") {
                applicationId = "com.nextcloud.client"
                dimension = "default"
            }

            register("gplay") {
                applicationId = "com.nextcloud.client"
                dimension = "default"
            }

            register("huawei") {
                applicationId = "com.nextcloud.client"
                dimension = "default"
            }

            register("versionDev") {
                applicationId = "com.nextcloud.android.beta"
                dimension = "default"
                versionCode = 20220322
                versionName = "20220322"
            }

            register("qa") {
                applicationId = "com.nextcloud.android.qa"
                dimension = "default"
                versionCode = 1
                versionName = "1"
            }
        }
    }

    applicationVariants.configureEach {
        outputs.configureEach {
            if (this is ApkVariantOutputImpl) this.outputFileName = "${this.baseName}-${this.versionCode}.apk"
        }
    }

    testOptions {
        unitTests.isReturnDefaultValues = true
        animationsDisabled = true
    }

    // adapt structure from Eclipse to Gradle/Android Studio expectations;
    // see http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Configuring-the-Structure
    packaging.resources {
        excludes.addAll(listOf("META-INF/LICENSE*", "META-INF/versions/9/OSGI-INF/MANIFEST*"))
        pickFirsts.add("MANIFEST.MF") // workaround for duplicated manifest on some dependencies
    }

    buildFeatures {
        buildConfig = true
        dataBinding = true
        viewBinding = true
        aidl = true
        compose = true
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    lint {
        abortOnError = true
        warningsAsErrors = true
        checkGeneratedSources = true
        disable.addAll(
            listOf(
                "MissingTranslation",
                "GradleDependency",
                "VectorPath",
                "IconMissingDensityFolder",
                "IconDensities",
                "GoogleAppIndexingWarning",
                "MissingDefaultResource",
                "InvalidPeriodicWorkRequestInterval",
                "StringFormatInvalid",
                "MissingQuantity",
                "IconXmlAndPng",
                "SelectedPhotoAccess",
                "UnsafeIntentLaunch"
            )
        )
        htmlOutput = layout.buildDirectory.file("reports/lint/lint.html").get().asFile
        htmlReport = true
    }

    sourceSets {
        // Adds exported schema location as test app assets.
        getByName("androidTest") {
            assets.srcDirs(files("$projectDir/schemas"))
        }
    }

}

kapt.useBuildCache = true

ksp.arg("room.schemaLocation", "$projectDir/schemas")

kotlin.compilerOptions.jvmTarget.set(JvmTarget.JVM_17)

spotless.kotlin {
    target("**/*.kt")
    ktlint()
}

detekt.config.setFrom("detekt.yml")

if (shotTest) configure<ShotExtension> {
    showOnlyFailingTestsInReports = ciBuild
    // CI environment renders some shadows slightly different from local VMs
    // Add a 0.5% tolerance to account for that
    tolerance = if (ciBuild) 0.1 else 0.0
}


spotbugs {
    ignoreFailures = true // should continue checking
    effort = Effort.MAX
    reportLevel = Confidence.valueOf("MEDIUM")
}

tasks.register<Checkstyle>("checkstyle") {
    configFile = file("${rootProject.projectDir}/checkstyle.xml")
    setConfigProperties(
        "checkstyleSuppressionsPath" to file("${rootProject.rootDir}/suppressions.xml").absolutePath
    )
    source("src")
    include("**/*.java")
    exclude("**/gen/**")
    classpath = files()
}

tasks.register<Pmd>("pmd") {
    ruleSetFiles = files("${rootProject.rootDir}/ruleset.xml")
    ignoreFailures = true // should continue checking
    ruleSets = emptyList()

    source("src")
    include("**/*.java")
    exclude("**/gen/**")

    reports {
        xml.outputLocation.set(layout.buildDirectory.file("reports/pmd/pmd.xml").get().asFile)
        html.outputLocation.set(layout.buildDirectory.file("reports/pmd/pmd.html").get().asFile)
    }
}

tasks.withType<SpotBugsTask>().configureEach {
    val variantNameCap = name.replace("spotbugs", "")
    val variantName = variantNameCap.substring(0, 1).lowercase() + variantNameCap.substring(1)
    dependsOn("compile${variantNameCap}Sources")

    classes = fileTree(
        layout.buildDirectory.get().asFile.toString() +
            "/intermediates/javac/${variantName}/compile${variantNameCap}JavaWithJavac/classes/"
    )
    excludeFilter.set(file("${project.rootDir}/scripts/analysis/spotbugs-filter.xml"))

    reports.create("xml") {
        required.set(true)
    }
    reports.create("html") {
        required.set(true)
        outputLocation.set(layout.buildDirectory.file("reports/spotbugs/spotbugs.html"))
        setStylesheet("fancy.xsl")
    }
}

// Run the compiler as a separate process
tasks.withType<JavaCompile>().configureEach {
    options.isFork = true

    // Enable Incremental Compilation
    options.isIncremental = true
}

tasks.withType<Test>().configureEach {
    // Run tests in parallel
    maxParallelForks = Runtime.getRuntime().availableProcessors().div(2)

    // increased logging for tests
    testLogging.events("passed", "skipped", "failed")
}

tasks.named("check").configure {
    dependsOn("checkstyle", "spotbugsGplayDebug", "pmd", "lint", "spotlessKotlinCheck", "detekt")
}

dependencies {
    // region Nextcloud library
    implementation(libs.android.library) {
        exclude(group = "org.ogce", module = "xpp3") // unused in Android and brings wrong Junit version
    }
    // endregion

    // region Splash Screen
    implementation(libs.splashscreen)
    // endregion

    // region Jetpack Compose
    implementation(platform(libs.compose.bom))
    implementation(libs.material.icons.core)
    implementation(libs.compose.ui)
    implementation(libs.compose.ui.graphics)
    implementation(libs.compose.material3)
    implementation(libs.compose.ui.tooling.preview)
    debugImplementation(libs.compose.ui.tooling)
    // endregion

    // region Media3
    implementation(libs.bundles.media3)
    // endregion

    // region Room
    implementation(libs.room.runtime)
    ksp(libs.room.compiler)
    androidTestImplementation(libs.room.testing)
    // endregion

    // region Espresso
    androidTestImplementation(libs.bundles.espresso)
    // endregion

    // region Glide
    implementation(libs.glide)
    ksp(libs.ksp)
    // endregion

    // region UI
    implementation(libs.bundles.ui)
    // endregion

    // region Worker
    implementation(libs.work.runtime)
    implementation(libs.work.runtime.ktx)
    // endregion

    // region Lifecycle
    implementation(libs.lifecycle.viewmodel.ktx)
    implementation(libs.lifecycle.service)
    implementation(libs.lifecycle.runtime.ktx)
    // endregion

    // region JUnit
    androidTestImplementation(libs.junit)
    androidTestImplementation(libs.rules)
    androidTestImplementation(libs.runner)
    androidTestUtil(libs.orchestrator)
    androidTestImplementation(libs.core.ktx)
    androidTestImplementation(libs.core.testing)
    // endregion

    // region other libraries
    compileOnly(libs.org.jbundle.util.osgi.wrapped.org.apache.http.client)
    implementation(libs.commons.httpclient.commons.httpclient) // remove after entire switch to lib v2
    implementation(libs.jackrabbit.webdav) // remove after entire switch to lib v2
    implementation(libs.constraintlayout)
    implementation(libs.legacy.support.v4)
    implementation(libs.material)
    implementation(libs.disklrucache)
    implementation(libs.juniversalchardet) // need this version for Android <7
    compileOnly(libs.annotations)
    implementation(libs.commons.io)
    implementation(libs.eventbus)
    implementation(libs.ez.vcard)
    implementation(libs.nnio)
    implementation(libs.bcpkix.jdk18on)
    implementation(libs.gson)
    implementation(libs.sectioned.recyclerview)
    implementation(libs.photoview)
    implementation(libs.android.gif.drawable)
    implementation(libs.qrcodescanner) // "com.github.blikoon:QRCodeScanner:0.1.2"
    implementation(libs.flexbox)
    implementation(libs.androidsvg)
    implementation(libs.annotation)
    implementation(libs.emoji.google)
    // endregion

    // region AppScan, document scanner not available on FDroid (generic) due to OpenCV binaries
    "gplayImplementation"(project(":appscan"))
    "huaweiImplementation"(project(":appscan"))
    "qaImplementation"(project(":appscan"))
    // endregion

    // region SpotBugs
    spotbugsPlugins(libs.findsecbugs.plugin)
    spotbugsPlugins(libs.fb.contrib)
    // endregion

    // region Dagger
    implementation(libs.dagger)
    implementation(libs.dagger.android)
    implementation(libs.dagger.android.support)
    ksp(libs.dagger.compiler)
    ksp(libs.dagger.processor)
    // endregion

    // region Crypto
    implementation(libs.conscrypt.android)
    // endregion

    // region Library
    implementation(libs.library)
    // endregion

    // region Shimmer
    implementation(libs.loaderviewlibrary)
    // endregion

    // region Markdown rendering
    implementation(libs.bundles.markdown.rendering)
    kapt(libs.prism4j.bundler)
    // endregion

    // region Image cropping / rotation
    implementation(libs.android.image.cropper)
    // endregion

    // region Maps
    implementation(libs.osmdroid.android)
    // endregion

    // region iCal4j
    implementation(libs.ical4j) {
        listOf("org.apache.commons", "commons-logging").forEach { groupName -> exclude(group = groupName) }
    }
    // endregion

    // region LeakCanary
    if (perfAnalysis) debugImplementation(libs.leakcanary)
    // endregion

    // region Local Unit Test
    testImplementation(libs.bundles.unit.test)
    // endregion

    // region Mocking support
    androidTestImplementation(libs.bundles.mocking)
    // endregion

    // region UIAutomator
    // UIAutomator - for cross-app UI tests, and to grant screen is turned on in Espresso tests
    // androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0"
    // fix conflict in dependencies; see http://g.co/androidstudio/app-test-app-conflict for details
    // androidTestImplementation("com.android.support:support-annotations:${supportLibraryVersion}"
    androidTestImplementation(libs.screengrab)
    // endregion

    // region Kotlin
    implementation(libs.kotlin.stdlib)
    // endregion

    // region Stateless
    implementation(libs.stateless4j)
    // endregion

    // region Google Play dependencies, upon each update first test: new registration, receive push
    "gplayImplementation"(libs.bundles.gplay)
    // endregion

    // region UI
    implementation(libs.ui)
    // endregion

    // region Image loading
    implementation(libs.coil)
    // endregion

    // kotlinx.serialization
    implementation(libs.kotlinx.serialization.json)
}
