import org.apache.tools.ant.taskdefs.condition.Os

import java.util.regex.Matcher
import java.util.regex.Pattern

def config = project.hasProperty("sentryCli") ? project.sentryCli : [];

gradle.projectsEvaluated {
    def releases = extractReleasesInfo()

    // separately we then hook into the bundle task of react native to inject
    // sourcemap generation parameters.  In case for whatever reason no release
    // was found for the asset folder we just bail.
    def bundleTasks = tasks.findAll { task -> task.name.startsWith("bundle") && task.name.endsWith("JsAndAssets") && !task.name.contains("Debug")}
    bundleTasks.each { bundleTask ->
        def shouldCleanUp
        def sourcemapOutput
        def bundleOutput
        def props = bundleTask.getProperties()
        def reactRoot = props.get("workingDir")

        (shouldCleanUp, bundleOutput, sourcemapOutput) = forceSourceMapOutputFromBundleTask(bundleTask)

        if (config.flavorAware) {
            println "**********************************"
            println "* Flavor aware sentry properties *"
            println "**********************************"
        }

        // Lets leave this here if we need to debug
        // println bundleTask.properties
        //     .sort{it.key}
        //     .collect{it}
        //     .findAll{!['class', 'active'].contains(it.key)}
        //     .join('\n')

        def currentVariants = extractCurrentVariants(bundleTask, releases)
        if (currentVariants == null) return

        def variant = null
        def releaseName = null
        def versionCodes = new ArrayList<Integer>(currentVariants.size())

        currentVariants.each { key, currentVariant ->
            variant = currentVariant[0]
            releaseName = currentVariant[1]
            versionCodes.push(currentVariant[2])
        }

        def nameCliTask = "${bundleTask.name}_SentryUpload"
        def nameCleanup = "${bundleTask.name}_SentryUploadCleanUp"

        /** Upload source map file to the sentry server via CLI call. */
        def cliTask = tasks.create(name: nameCliTask, type: Exec) {
            description = "upload debug symbols to sentry"
            group = 'sentry.io'

            workingDir reactRoot

            def propertiesFile = "$reactRoot/android/sentry.properties"
            if (config.flavorAware) {
                propertiesFile = "$reactRoot/android/sentry-${variant}.properties"
                project.logger.info("For $variant using: $propertiesFile")
            } else {
                environment("SENTRY_PROPERTIES", propertiesFile)
            }

            Properties sentryProps = new Properties()
            try {
                sentryProps.load(new FileInputStream(propertiesFile))
            } catch (FileNotFoundException e) {
                project.logger.info("file not found '$propertiesFile' for '$variant'")
            }
            def cliExecutable = sentryProps.get("cli.executable", "$reactRoot/node_modules/@sentry/cli/bin/sentry-cli")

            // fix path separator for Windows
            if (Os.isFamily(Os.FAMILY_WINDOWS)) {
                cliExecutable = cliExecutable.replaceAll("/", "\\\\")
            }

            //
            // based on:
            //   https://github.com/getsentry/sentry-cli/blob/master/src/commands/react_native_gradle.rs
            //
            def args = [cliExecutable]

            args.addAll( !config.logLevel ? [] : [
                "--log-level", config.logLevel      // control verbosity of the output
                ])
            args.addAll(!config.flavorAware ? [] : [
                "--url", sentryProps.get("defaults.url"),
                "--auth-token", sentryProps.get("auth.token")
                ])
            args.addAll(["react-native", "gradle",
                "--bundle", bundleOutput,           // The path to a bundle that should be uploaded.
                "--sourcemap", sourcemapOutput,     // The path to a sourcemap that should be uploaded.
                "--release", releaseName            // The name of the release to publish.
                ])
            args.addAll(!config.flavorAware ? [] : [
                "--org", sentryProps.get("defaults.org"),
                "--project", sentryProps.get("defaults.project")
                ])

            // The names of the distributions to publish. Can be supplied multiple times.
            versionCodes.each { versionCode -> args.addAll(["--dist", versionCode]) }

            project.logger.info("Sentry-CLI arguments: ${args}")

            def osCompatibility = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c', 'node'] : []
            commandLine(*osCompatibility, *args)

            enabled true
        }

        /** Delete sourcemap files */
        def cliCleanUpTask = tasks.create(name: nameCleanup, type: Delete) {
            description = "clean up extra sourcemap"
            group = 'sentry.io'

            delete sourcemapOutput
        }

        // dependsOn, mustRunAfter, shouldRunAfter, doFirst, doLast, finalizedBy
        // bundleTask --> cliTask
        bundleTask.finalizedBy cliTask

        // register clean task extension
        cliCleanUpTask.onlyIf { shouldCleanUp }
        project.tasks.clean.dependsOn cliCleanUpTask
    }
}

/** Compose lookup map of build variants - to - outputs. */
def extractReleasesInfo() {
    def releases = [:]

    android.applicationVariants.each { variant ->
        def releaseName = "${variant.getApplicationId()}-${variant.getVersionName()}"

        variant.outputs.each { output ->
            def versionCode = output.getVersionCode()
            def variantName = variant.getName()
            def outputName = output.getName()
            if (releases[variantName] == null) {
                releases[variantName] = [:]
            }
            releases[variantName][outputName] = [outputName, releaseName, versionCode]
        }
    }

    return releases
}

/** Extract from arguments collection bundle and sourcemap files output names. */
static extractBundleTaskArguments(cmdArgs){
    def bundleOutput = null
    def sourcemapOutput = null

    cmdArgs.eachWithIndex { String arg, int i ->
        if (arg == "--bundle-output") {
            bundleOutput = cmdArgs[i + 1]
        } else if (arg == "--sourcemap-output") {
            sourcemapOutput = cmdArgs[i + 1]
        }
    }

    return [ bundleOutput, sourcemapOutput ]
}

/** Force Bundle task to produce sourcemap files if they are not pre-configured by user yet. */
def forceSourceMapOutputFromBundleTask(bundleTask){
    def props = bundleTask.getProperties()
    def cmd = props.get("commandLine") as List<String>
    def cmdArgs = props.get("args") as List<String>
    def shouldCleanUp = false
    def bundleOutput = null
    def sourcemapOutput = null

    (bundleOutput, sourcemapOutput) = extractBundleTaskArguments(cmdArgs)

    if (sourcemapOutput == null) {
        sourcemapOutput = bundleOutput + ".map"

        cmd.addAll(["--sourcemap-output", sourcemapOutput])
        cmdArgs.addAll(["--sourcemap-output", sourcemapOutput])

        shouldCleanUp = true

        bundleTask.setProperty("commandLine", cmd)
        bundleTask.setProperty("args", cmdArgs)

        project.logger.info("forced sourcemap file output for `${bundleTask.name}` task")
    } else {
        project.logger.info("Info: used pre-configured source map files: ${sourcemapOutput}")
    }

    return [shouldCleanUp, bundleOutput, sourcemapOutput]
}

/** compose array with one item - current build flavor name */
static extractCurrentVariants(bundleTask, releases) {
    // examples: bundleLocalReleaseJsAndAssets, bundleYellowDebugJsAndAssets
    def pattern = Pattern.compile("bundle([A-Z][A-Za-z0-9_]+)JsAndAssets")

    def currentRelease = ""

    Matcher matcher = pattern.matcher(bundleTask.name)
    if (matcher.find()) {
        def match = matcher.group(1)
        currentRelease = match.substring(0, 1).toLowerCase() + match.substring(1)
    }

    def currentVariants = null
    releases.each { key, release ->
        if (key.equalsIgnoreCase(currentRelease)) {
            currentVariants = release
        }
    }

    return currentVariants
}
