package com.sonymusic

import com.lesfurets.jenkins.unit.BasePipelineTest
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

import static org.hamcrest.CoreMatchers.not
import static org.hamcrest.MatcherAssert.assertThat
import static org.hamcrest.core.StringContains.containsString
import static org.junit.jupiter.api.Assertions.*

class DockerToEcrTest extends BasePipelineTest {
    def dockerToEcr
    def ecrAccountId = '1234567890'
    def imageName = 'service'
    def imageTag = 'abcdef'

    @BeforeEach
    void setUp() {
        super.setUp()
        dockerToEcr = loadScript("vars/dockerToEcr.groovy")
        helper.registerAllowedMethod('sh')
        helper.registerAllowedMethod('withEcr', [Closure])
        binding.getVariable('env')['GIT_COMMIT'] = '123456'
    }

    @Test
    void testCallWithNoArgs() {
        // Assert error is thrown since required args are not supplied
        assertThrows(AssertionError.class, {
            dockerToEcr.call(dummy: 'dummy')
        })
    }

    @Test
    void testCallWithBadRegion() {
        // Assert error is thrown since awsRegions is a string
        assertThrows(AssertionError.class, {
            dockerToEcr.call(
                awsRegions: 'dummy',
                dockerBuildContext: '.',
                ecrAccountId: ecrAccountId,
                imageName: imageName,
                imageTag: imageTag,
            )
        })
    }

    @Test
    void testCallSuccess() {
        // This one should succeed and return nothing
        assertNull(dockerToEcr(
            awsRegions: ['dummy'],
            dockerBuildContext: '.',
            ecrAccountId: ecrAccountId,
            imageName: imageName,
            imageTag: imageTag,
        ))

        def shSteps = helper.callStack.findAll{ call ->
            call.methodName == 'sh'
        }

        // Do a simple test that the previous method call made it to the shell block
        assertEquals(3, shSteps.size())

        /*
        * Extra spaces between build and --tag are deliberate since docker build command favors readability in the code.
        * As a result, it adds spaces between variables which exist even when those are not defined.
        */

        def buildString = "docker build  --build-arg GIT_COMMIT=123456 --build-arg VERSION=abcdef --build-arg REPOSITORY_URL=github.com/theorchard/service --build-arg version=abcdef  --provenance=false --tag ${imageName}:${imageTag} --file Dockerfile --no-cache=true --pull null/dockerToEcr/service__abcdef/."

        assertThat(shSteps[0].argsToString(), containsString(buildString))
        assertThat(shSteps[1].argsToString(), containsString("docker push ${ecrAccountId}.dkr.ecr.dummy.amazonaws.com/${imageName}:${imageTag}"))
        assertThat(shSteps[2].argsToString(), containsString("docker push ${ecrAccountId}.dkr.ecr.dummy.amazonaws.com/${imageName}:latest"))
    }

    @Test
    void testCallWithMalformedBuildArgs() {
        assertThrows(AssertionError.class, {
            dockerToEcr.call(
                awsRegions: ['dummy'],
                dockerBuildContext: '.',
                ecrAccountId: ecrAccountId,
                imageName: imageName,
                imageTag: imageTag,
                dockerBuildArgs: [
                    'aStringWithoutEqualsSign',
                ]
            )
        })
    }

    @Test
    void testCallWithDefaultBuildArgsAndHttpsGitUrlSet() {
        binding.getVariable('env')['GIT_URL'] = 'https://github.com/theorchard/foo.git'

        assertNull(dockerToEcr(
            awsRegions: ['dummy'],
            dockerBuildContext: '.',
            ecrAccountId: ecrAccountId,
            imageName: imageName,
            imageTag: imageTag
        ))

        assertEquals(3, helper.callStack.findAll{ it.methodName == 'sh' }.size())

        def dockerBuildCommand = helper.callStack.find { it.methodName == 'sh' && it.argsToString()?.contains('docker build') }
        assertNotNull(dockerBuildCommand)

        def expectedArgs = '--build-arg REPOSITORY_URL=github.com/theorchard/foo'
        assertThat(dockerBuildCommand.argsToString(), containsString(expectedArgs))
    }

    @Test
    void testCallWithDefaultBuildArgsAndSshGitUrlSet() {
        binding.getVariable('env')['GIT_URL'] = 'git@github.com:theorchard/foo.git'

        assertNull(dockerToEcr(
            awsRegions: ['dummy'],
            dockerBuildContext: '.',
            ecrAccountId: ecrAccountId,
            imageName: imageName,
            imageTag: imageTag
        ))

        assertEquals(3, helper.callStack.findAll{ it.methodName == 'sh' }.size())

        def dockerBuildCommand = helper.callStack.find { it.methodName == 'sh' && it.argsToString()?.contains('docker build') }
        assertNotNull(dockerBuildCommand)

        def expectedArgs = '--build-arg REPOSITORY_URL=github.com/theorchard/foo'
        assertThat(dockerBuildCommand.argsToString(), containsString(expectedArgs))
    }

    @Test
    void testCallWithBuildArgsSuccess() {
        // This one should succeed and return nothing
        assertNull(dockerToEcr(
            awsRegions: ['dummy'],
            dockerBuildContext: '.',
            ecrAccountId: ecrAccountId,
            imageName: imageName,
            imageTag: imageTag,
            dockerBuildArgs: [
                TOKEN: '1234567890',
                API_KEY: 'abcdefghijk'
            ]
        ))

        // Do a simple test that the previous method call made it to the shell block
        assertEquals(3, helper.callStack.findAll{ it.methodName == 'sh' }.size())

        def dockerBuildCommand = helper.callStack.find { it.methodName == 'sh' && it.argsToString()?.contains('docker build') }
        assertNotNull(dockerBuildCommand)

        def expectedArgs = '--build-arg GIT_COMMIT=123456 --build-arg VERSION=abcdef --build-arg REPOSITORY_URL=github.com/theorchard/service --build-arg version=abcdef --build-arg TOKEN=1234567890 --build-arg API_KEY=abcdefghijk'
        assertThat(dockerBuildCommand.argsToString(), containsString(expectedArgs))
    }

    @Test
    void testCallWithMalformedSecrets() {
        assertThrows(AssertionError.class, {
            dockerToEcr.call(
                awsRegions: ['dummy'],
                dockerBuildContext: '.',
                ecrAccountId: ecrAccountId,
                imageName: imageName,
                imageTag: imageTag,
                dockerBuildSecrets: [
                    [badkey: 'testid1', src: 'testsecret1'],
                    [otherbadkey: 'testid2', src: 'testsecret2'],
                ]
            )
        })
    }

    @Test
    void testCallWithMalformedAdditonalContexts() {
        assertThrows(AssertionError.class, {
            dockerToEcr.call(
                awsRegions: ['dummy'],
                dockerBuildContext: '.',
                ecrAccountId: ecrAccountId,
                imageName: imageName,
                imageTag: imageTag,
                additionalContexts: [
                    'aStringWithoutEqualsSign',
                ]
            )
        })
    }

    @Test
    void testCallWithAdditionalContextsSuccess() {
        // This one should succeed and return nothing
        assertNull(dockerToEcr(
            awsRegions: ['dummy'],
            dockerBuildContext: '.',
            ecrAccountId: ecrAccountId,
            imageName: imageName,
            imageTag: imageTag,
            additionalContexts: [
                foo: '../some_dir',
                bar: 'docker-image://alpine:3.15'
            ]
        ))

        // Do a simple test that the previous method call made it to the shell block
        assertEquals(3, helper.callStack.findAll{ it.methodName == 'sh' }.size())

        def dockerBuildCommand = helper.callStack.find { it.methodName == 'sh' && it.argsToString()?.contains('docker build') }
        assertNotNull(dockerBuildCommand)

        def expectedArgs = '--build-context foo=../some_dir --build-context bar=docker-image://alpine:3.15'
        assertThat(dockerBuildCommand.argsToString(), containsString(expectedArgs))
    }

    @Test
    void testCallWithSecretsSuccess() {
        // This one should succeed and return nothing
        assertNull(dockerToEcr(
            awsRegions: ['dummy'],
            dockerBuildContext: '.',
            ecrAccountId: ecrAccountId,
            imageName: imageName,
            imageTag: imageTag,
            dockerBuildSecrets: [
                [id: 'testid1', src: 'testsecret1'],
                [id: 'testid2', env: 'testsecret2'],
            ]
        ))

        // Do a simple test that the previous method call made it to the shell block
        assertEquals(3, helper.callStack.findAll{ it.methodName == 'sh' }.size())

        // Test that the environment variables set by withEnv match
        def withEnv = [
            "DOCKER_BUILDKIT=1",
        ]
        helper.callStack.findAll{ it.methodName == 'withEnv' }.last().args[0].eachWithIndex { envVar, index ->
            assertEquals(envVar.toString(), withEnv[index].toString())
        }

        def dockerBuildCommand = helper.callStack.find { it.methodName == 'sh' && it.argsToString()?.contains('docker build') }
        assertNotNull(dockerBuildCommand)

        def expectedArgs = '--secret id=testid1,src=testsecret1 --secret id=testid2,env=testsecret2'
        assertThat(dockerBuildCommand.argsToString(), containsString(expectedArgs))
    }

    @Test
    void testCallWithPushLatestDisabled() {
        dockerToEcr(
            awsRegions: ['dummy'],
            dockerBuildContext: '.',
            ecrAccountId: ecrAccountId,
            imageName: imageName,
            imageTag: imageTag,
            dockerBuildArgs: [
                TOKEN: '1234567890',
                API_KEY: 'abcdefghijk'
            ],
            pushLatest: false
        )

        def shSteps = helper.callStack.findAll{ call ->
            call.methodName == 'sh'
        }
        assertEquals(2, shSteps.size())
        assertThat(shSteps[0].argsToString(), not(containsString('latest')))
        assertThat(shSteps[1].argsToString(), not(containsString('latest')))
    }

    @Test
    void testCallWithProvenanceTrue() {
        assertNull(dockerToEcr(
            awsRegions: ['dummy'],
            dockerBuildContext: '.',
            ecrAccountId: ecrAccountId,
            imageName: imageName,
            imageTag: imageTag,
            dockerProvenance: true
        ))
        def dockerBuildCommand = helper.callStack.find { it.methodName == 'sh' && it.argsToString()?.contains('docker build') }
        assertNotNull(dockerBuildCommand)
        assertThat(dockerBuildCommand.argsToString(), containsString('--provenance=true'))
    }

    @Test
    void testCallWithMalformedRegistryCredentials() {
        assertThrows(AssertionError.class, {
            dockerToEcr.call(
                awsRegions: ['dummy'],
                dockerBuildContext: '.',
                ecrAccountId: ecrAccountId,
                imageName: imageName,
                imageTag: imageTag,
                registryCredentials: [
                    [username: 'myuser'],  // missing password
                ]
            )
        })
    }

    @Test
    void testCallWithRegistryCredentialsSuccess() {
        assertNull(dockerToEcr(
            awsRegions: ['dummy'],
            dockerBuildContext: '.',
            ecrAccountId: ecrAccountId,
            imageName: imageName,
            imageTag: imageTag,
            registryCredentials: [
                [username: 'user1', password: 'pass1'],
                [username: 'user2', password: 'pass2'],
            ]
        ))

        def withEnvCalls = helper.callStack.findAll { it.methodName == 'withEnv' }
        assertEquals(2, withEnvCalls.size())
        assertThat(withEnvCalls[0].args[0].toString(), containsString('REGISTRY_USERNAME=user1'))
        assertThat(withEnvCalls[0].args[0].toString(), containsString('REGISTRY_PASSWORD=pass1'))
        assertThat(withEnvCalls[1].args[0].toString(), containsString('REGISTRY_USERNAME=user2'))
        assertThat(withEnvCalls[1].args[0].toString(), containsString('REGISTRY_PASSWORD=pass2'))

        def loginSteps = helper.callStack.findAll { it.methodName == 'sh' && it.argsToString().contains('echo $REGISTRY_PASSWORD | docker login --username $REGISTRY_USERNAME --password-stdin') }
        assertEquals(2, loginSteps.size())
    }

    @Test
    void testCallWithRegistryCredentialsWithUrl() {
        assertNull(dockerToEcr(
            awsRegions: ['dummy'],
            dockerBuildContext: '.',
            ecrAccountId: ecrAccountId,
            imageName: imageName,
            imageTag: imageTag,
            registryCredentials: [
                [username: 'user1', password: 'pass1', url: 'registry.example.com'],
            ]
        ))

        def loginStep = helper.callStack.find { it.methodName == 'sh' && it.argsToString().contains('echo $REGISTRY_PASSWORD | docker login --username $REGISTRY_USERNAME --password-stdin') }
        assertNotNull(loginStep)
    }

    @Test
    void testCallWithProvenanceFalse() {
        assertNull(dockerToEcr(
            awsRegions: ['dummy'],
            dockerBuildContext: '.',
            ecrAccountId: ecrAccountId,
            imageName: imageName,
            imageTag: imageTag,
            dockerProvenance: false
        ))
        def dockerBuildCommand = helper.callStack.find { it.methodName == 'sh' && it.argsToString()?.contains('docker build') }
        assertNotNull(dockerBuildCommand)
        assertThat(dockerBuildCommand.argsToString(), containsString('--provenance=false'))
    }
}
