import groovy.json.JsonSlurperClassic

def runPipeline() {
    try {
        ansiColor('xterm') {
            runStages();
        }
    } catch(err) {
        echo "Error: ${err}"
        currentBuild.result = "FAILED"
    }
}

def pullDockerImage(imageName) {
    def result = sh(script: "docker pull ${imageName}", returnStatus: true)

    if (result != 0) {
        throw new Exception("Failed to pull image[${imageName}]")
    }
}

def buildDockerfile(dockerfilePath = "Dockerfile", imageName) {
    def buildCmd = "docker build -f ${dockerfilePath} -t ${imageName} ."
    echo "${buildCmd}"

    def result = sh(script: buildCmd, returnStatus: true)

    if (result != 0) {
        throw new Exception("Failed to build image[${imageName}] from '${dockerfilePath}'")
    }
}

def runCmdOnDockerImage(imageName, cmd, run_opts = '') {
    def result = sh(script: "docker run ${run_opts} -i ${imageName} sh -c '${cmd}'", returnStatus: true)

    if(result != 0) {
        throw new Exception("Failed to run cmd[${cmd}] on image[${imageName}]")
    }
}

def calculateGithubInfo() {
    return [
        branch: env.BRANCH_NAME,
        sha: sh(returnStdout: true, script: 'git rev-parse HEAD').trim(),
        tag: null,
        isPR: "${env.CHANGE_URL}".contains('/pull/')
    ]
}

def getParallelInstrumentationTests(testDir, parallelCount, imageName) {
    def integrationTests = [:]
    def testCount = sh(script: "ls ${testDir} | wc -l", returnStdout: true).trim().toInteger()
    def testPerParallel = testCount.intdiv(parallelCount) + 1

    def ignoredTests = 'CatalystNativeJavaToJSReturnValuesTestCase|CatalystUIManagerTestCase|CatalystMeasureLayoutTest|CatalystNativeJavaToJSArgumentsTestCase|CatalystNativeJSToJavaParametersTestCase|ReactScrollViewTestCase|ReactHorizontalScrollViewTestCase|ViewRenderingTestCase';

    for (def x = 0; (x*testPerParallel) < testCount; x++) {
        def offset = x
        integrationTests["android integration tests: ${offset}"] = {
            run: {
                runCmdOnDockerImage(imageName, "bash /app/ContainerShip/scripts/run-android-docker-instrumentation-tests.sh --offset=${offset} --count=${testPerParallel} --ignore=\"${ignoredTests}\"", '--privileged --rm')
            }
        }
    }

    return integrationTests
}

def runStages() {
    def buildInfo = [
        image: [
            name: "facebook/react-native",
            tag: null
        ],
        scm: [
            branch: null,
            sha: null,
            tag: null,
            isPR: false
        ]
    ]

    node {
        def jsDockerBuild, androidDockerBuild
        def jsTag, androidTag, jsImageName, androidImageName, parallelInstrumentationTests

        try {
            stage('Setup') {
                parallel(
                    'pull images': {
                        pullDockerImage('containership/android-base:latest')
                    }
                )
            }

            stage('Build') {
                checkout scm

                def githubInfo = calculateGithubInfo()
                buildInfo.scm.branch = githubInfo.branch
                buildInfo.scm.sha = githubInfo.sha
                buildInfo.scm.tag = githubInfo.tag
                buildInfo.scm.isPR = githubInfo.isPR
                buildInfo.image.tag = "${buildInfo.scm.sha}-${env.BUILD_TAG.replace(" ", "-").replace("/", "-").replace("%2F", "-")}"

                jsTag = "${buildInfo.image.tag}"
                androidTag = "${buildInfo.image.tag}"
                jsImageName = "${buildInfo.image.name}-js:${jsTag}"
                androidImageName = "${buildInfo.image.name}-android:${androidTag}"

                parallelInstrumentationTests = getParallelInstrumentationTests('./ReactAndroid/src/androidTest/java/com/facebook/react/tests', 3, androidImageName)

                parallel(
                    'javascript build': {
                        jsDockerBuild = docker.build("${jsImageName}", "-f ContainerShip/Dockerfile.javascript .")
                    },
                    'android build': {
                        androidDockerBuild = docker.build("${androidImageName}", "-f ContainerShip/Dockerfile.android .")
                    }
                )

            }

            stage('Tests JS') {
                try {
                    parallel(
                        'javascript flow': {
                            runCmdOnDockerImage(jsImageName, 'yarn run flow -- check', '--rm')
                        },
                        'javascript tests': {
                            runCmdOnDockerImage(jsImageName, 'yarn test --maxWorkers=4', '--rm')
                        },
                        'documentation tests': {
                            runCmdOnDockerImage(jsImageName, 'cd website && yarn test', '--rm')
                        },
                        'documentation generation': {
                            runCmdOnDockerImage(jsImageName, 'cd website && node ./server/generate.js', '--rm')
                        }
                    )
                } catch(e) {
                    currentBuild.result = "FAILED"
                    echo "Test JS Stage Error: ${e}"
                }
            }

            stage('Tests Android') {
                try {
                    parallel(
                        'android unit tests': {
                            runCmdOnDockerImage(androidImageName, 'bash /app/ContainerShip/scripts/run-android-docker-unit-tests.sh', '--privileged --rm')
                        },
                        'android e2e tests': {
                            runCmdOnDockerImage(androidImageName, 'bash /app/ContainerShip/scripts/run-ci-e2e-tests.sh --android --js', '--privileged --rm')
                        }
                    )
                } catch(e) {
                    currentBuild.result = "FAILED"
                    echo "Tests Android Stage Error: ${e}"
                }
            }

            stage('Tests Android Instrumentation') {
                // run all tests in parallel
                try {
                    parallel(parallelInstrumentationTests)
                } catch(e) {
                    currentBuild.result = "FAILED"
                    echo "Tests Android Instrumentation Stage Error: ${e}"
                }
            }

            stage('Cleanup') {
                cleanupImage(jsDockerBuild)
                cleanupImage(androidDockerBuild)
            }
        } catch(err) {
            cleanupImage(jsDockerBuild)
            cleanupImage(androidDockerBuild)

            throw err
        }
    }

}

def isMasterBranch() {
    return env.GIT_BRANCH == 'master'
}

def gitCommit() {
    return sh(returnStdout: true, script: 'git rev-parse HEAD').trim()
}

def cleanupImage(image) {
    if (image) {
        try {
            sh "docker ps -a | awk '{ print \$1,\$2 }' | grep ${image.id} | awk '{print \$1 }' | xargs -I {} docker rm {}"
            sh "docker rmi -f ${image.id}"
        } catch(e) {
            echo "Error cleaning up ${image.id}"
            echo "${e}"
        }
    }
}

runPipeline()