import com.sap.piper.k8s.ContainerMap
import com.sap.piper.JenkinsUtils

import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain

import util.BasePiperTest
import util.JenkinsLoggingRule
import util.JenkinsReadYamlRule
import util.JenkinsShellCallRule
import util.JenkinsStepRule
import util.PluginMock
import util.Rules

import static org.hamcrest.Matchers.*
import static org.junit.Assert.assertThat
import static org.junit.Assert.assertTrue
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertFalse

class DockerExecuteTest extends BasePiperTest {
    private DockerMock docker
    private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this)
    private JenkinsStepRule stepRule = new JenkinsStepRule(this)
    private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this)

    @Rule
    public RuleChain ruleChain = Rules
        .getCommonRules(this)
        .around(new JenkinsReadYamlRule(this))
        .around(loggingRule)
        .around(stepRule)
        .around(shellRule)

    def bodyExecuted
    def containerName

    @Before
    void init() {
        bodyExecuted = false
        docker = new DockerMock()
        JenkinsUtils.metaClass.static.isPluginActive = {def s -> new PluginMock(s).isActive()}
        binding.setVariable('docker', docker)
        shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "docker .*", 0)
    }

    @Test
    void testExecuteInsideContainerOfExistingPod() throws Exception {
        helper.registerAllowedMethod('container', [String.class, Closure.class], { String container, Closure body ->
            containerName = container
            body()
        })
        binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true'])
        ContainerMap.instance.setMap(['testpod': ['maven:3.5-jdk-8-alpine': 'mavenexec']])
        stepRule.step.dockerExecute(script: nullScript,
            dockerImage: 'maven:3.5-jdk-8-alpine',
            dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
            bodyExecuted = true
        }
        assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Container'))
        assertEquals('mavenexec', containerName)
        assertTrue(bodyExecuted)
     }

    @Test
    void testExecuteInsideNewlyCreatedPod() throws Exception {
        helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() })
        binding.setVariable('env', [ON_K8S: 'true'])
        ContainerMap.instance.setMap(['testpod': ['maven:3.5-jdk-8-alpine': 'mavenexec']])
        stepRule.step.dockerExecute(script: nullScript,
            dockerImage: 'maven:3.5-jdk-8-alpine',
            dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
            bodyExecuted = true
        }
        assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
        assertTrue(bodyExecuted)
    }

    @Test
    void testExecuteInsidePodWithEmptyContainerMap() throws Exception {
        helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() })
        binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true'])
        ContainerMap.instance.setMap([:])
        stepRule.step.dockerExecute(script: nullScript,
            dockerImage: 'maven:3.5-jdk-8-alpine',
            dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
            bodyExecuted = true
        }
        assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
        assertTrue(bodyExecuted)
    }

    @Test
    void testExecuteInsidePodWithStageKeyEmptyValue() throws Exception {
        helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { Map config, Closure body -> body() })
        binding.setVariable('env', [POD_NAME: 'testpod', ON_K8S: 'true'])
        ContainerMap.instance.setMap(['testpod':[:]])
        stepRule.step.dockerExecute(script: nullScript,
            dockerImage: 'maven:3.5-jdk-8-alpine',
            dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
            bodyExecuted = true
        }
        assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
        assertTrue(bodyExecuted)
    }

    @Test
    void testExecuteInsidePodWithCustomCommandAndShell() throws Exception {
        Map kubernetesConfig = [:]
        helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], {Map config, Closure body ->
            kubernetesConfig = config
            return body()
        })
        binding.setVariable('env', [ON_K8S: 'true'])
        stepRule.step.dockerExecute(
            script: nullScript,
            containerCommand: '/busybox/tail -f /dev/null',
            containerShell: '/busybox/sh',
            dockerImage: 'maven:3.5-jdk-8-alpine'
        ){
            bodyExecuted = true
        }
        assertTrue(loggingRule.log.contains('Executing inside a Kubernetes Pod'))
        assertThat(kubernetesConfig.containerCommand, is('/busybox/tail -f /dev/null'))
        assertThat(kubernetesConfig.containerShell, is('/busybox/sh'))
        assertTrue(bodyExecuted)
    }

    @Test
    void testExecuteInsideDockerContainer() throws Exception {
        stepRule.step.dockerExecute(script: nullScript, dockerImage: 'maven:3.5-jdk-8-alpine') {
            bodyExecuted = true
        }
        assertEquals('maven:3.5-jdk-8-alpine', docker.getImageName())
        assertTrue(docker.isImagePulled())
        assertEquals('--env http_proxy --env https_proxy --env no_proxy --env HTTP_PROXY --env HTTPS_PROXY --env NO_PROXY', docker.getParameters().trim())
        assertTrue(bodyExecuted)
    }

    @Test
    void testSkipDockerImagePull() throws Exception {
        nullScript.commonPipelineEnvironment.configuration = [steps:[dockerExecute:[dockerPullImage: false]]]
        stepRule.step.dockerExecute(
            script: nullScript,
            dockerImage: 'maven:3.5-jdk-8-alpine'
        ) {
            bodyExecuted = true
        }
        assertThat(docker.imagePullCount, is(0))
        assertThat(bodyExecuted, is(true))
    }

    @Test
    void testSkipSidecarImagePull() throws Exception {
        stepRule.step.dockerExecute(
            script: nullScript,
            dockerName: 'maven',
            dockerImage: 'maven:3.5-jdk-8-alpine',
            sidecarEnvVars: ['testEnv':'testVal'],
            sidecarImage: 'selenium/standalone-chrome',
            sidecarVolumeBind: ['/dev/shm':'/dev/shm'],
            sidecarName: 'testAlias',
            sidecarPorts: ['4444':'4444', '1111':'1111'],
            sidecarPullImage: false
        ) {
            bodyExecuted = true
        }
        assertThat(docker.imagePullCount, is(1))
        assertThat(bodyExecuted, is(true))
    }

    @Test
    void testExecuteInsideDockerContainerWithParameters() throws Exception {
        stepRule.step.dockerExecute(script: nullScript,
                      dockerImage: 'maven:3.5-jdk-8-alpine',
                      dockerOptions: '-description=lorem ipsum',
                      dockerVolumeBind: ['my_vol': '/my_vol'],
                      dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
            bodyExecuted = true
        }
        assertTrue(docker.getParameters().contains('--env https_proxy '))
        assertTrue(docker.getParameters().contains('--env http_proxy=http://proxy:8000'))
        assertTrue(docker.getParameters().contains('description=lorem\\ ipsum'))
        assertTrue(docker.getParameters().contains('--volume my_vol:/my_vol'))
        assertTrue(bodyExecuted)
    }

    @Test
    void testExecuteInsideDockerContainerWithDockerOptionsList() throws Exception {
        stepRule.step.dockerExecute(script: nullScript,
            dockerImage: 'maven:3.5-jdk-8-alpine',
            dockerOptions: ['-it', '--network=my-network', 'description=lorem ipsum'],
            dockerEnvVars: ['http_proxy': 'http://proxy:8000']) {
            bodyExecuted = true
        }
        assertTrue(docker.getParameters().contains('--env http_proxy=http://proxy:8000'))
        assertTrue(docker.getParameters().contains('-it'))
        assertTrue(docker.getParameters().contains('--network=my-network'))
        assertTrue(docker.getParameters().contains('description=lorem\\ ipsum'))
    }

    @Test
    void testDockerNotInstalledResultsInLocalExecution() throws Exception {
        shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "docker .*", 1)
        stepRule.step.dockerExecute(script: nullScript,
            dockerOptions: '-it') {
            bodyExecuted = true
        }
        assertTrue(loggingRule.log.contains('Cannot connect to docker daemon'))
        assertTrue(loggingRule.log.contains('Running on local environment'))
        assertTrue(bodyExecuted)
        assertFalse(docker.isImagePulled())
    }

    @Test
    void testSidecarDefault(){
        stepRule.step.dockerExecute(
            script: nullScript,
            dockerName: 'maven',
            dockerImage: 'maven:3.5-jdk-8-alpine',
            sidecarEnvVars: ['testEnv':'testVal'],
            sidecarImage: 'selenium/standalone-chrome',
            sidecarVolumeBind: ['/dev/shm':'/dev/shm'],
            sidecarName: 'testAlias',
            sidecarPorts: ['4444':'4444', '1111':'1111']
        ) {
            bodyExecuted = true
        }

        assertThat(bodyExecuted, is(true))
        assertThat(docker.imagePullCount, is(2))
        assertThat(docker.sidecarParameters, allOf(
            containsString('--env testEnv=testVal'),
            containsString('--volume /dev/shm:/dev/shm'),
            containsString('--network sidecar-'),
            containsString('--network-alias testAlias')
        ))
        assertThat(docker.parameters, allOf(
            containsString('--network sidecar-'),
            containsString('--network-alias maven')
        ))
    }

    @Test
    void testSidecarHealthCheck(){
        stepRule.step.dockerExecute(
            script: nullScript,
            dockerImage: 'maven:3.5-jdk-8-alpine',
            sidecarImage: 'selenium/standalone-chrome',
            sidecarName: 'testAlias',
            sidecarReadyCommand: "isReady.sh"
        ) {}
        assertThat(shellRule.shell, hasItem("docker exec uniqueId isReady.sh"))
    }

    @Test
    void testSidecarKubernetes(){
        boolean dockerExecuteOnKubernetesCalled = false
        binding.setVariable('env', [ON_K8S: 'true'])
        helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { params, body ->
            dockerExecuteOnKubernetesCalled = true
            assertThat(params.containerCommands['selenium/standalone-chrome'], is(''))
            assertThat(params.containerEnvVars, allOf(hasEntry('selenium/standalone-chrome', ['testEnv': 'testVal']),hasEntry('maven:3.5-jdk-8-alpine', null)))
            assertThat(params.containerMap, allOf(hasEntry('maven:3.5-jdk-8-alpine', 'maven'), hasEntry('selenium/standalone-chrome', 'selenium')))
            assertThat(params.containerName, is('maven'))
            assertThat(params.containerPortMappings['selenium/standalone-chrome'], hasItem(allOf(hasEntry('containerPort', 4444), hasEntry('hostPort', 4444))))
            assertThat(params.containerWorkspaces['maven:3.5-jdk-8-alpine'], is('/home/piper'))
            assertThat(params.containerWorkspaces['selenium/standalone-chrome'], is(''))
            body()
        })
        stepRule.step.dockerExecute(
            script: nullScript,
            containerPortMappings: [
                'selenium/standalone-chrome': [[name: 'selPort', containerPort: 4444, hostPort: 4444]]
            ],
            dockerImage: 'maven:3.5-jdk-8-alpine',
            dockerName: 'maven',
            dockerWorkspace: '/home/piper',
            sidecarEnvVars: ['testEnv':'testVal'],
            sidecarImage: 'selenium/standalone-chrome',
            sidecarName: 'selenium',
            sidecarVolumeBind: ['/dev/shm':'/dev/shm']
        ) {
            bodyExecuted = true
        }
        assertThat(bodyExecuted, is(true))
        assertThat(dockerExecuteOnKubernetesCalled, is(true))
    }

    @Test
    void testSidecarKubernetesHealthCheck(){
        binding.setVariable('env', [ON_K8S: 'true'])

        helper.registerAllowedMethod('dockerExecuteOnKubernetes', [Map.class, Closure.class], { params, body ->
            body()
        })

        def containerCalled = false
        helper.registerAllowedMethod('container', [Map.class, Closure.class], { params, body ->
            containerCalled = true
            assertThat(params.name, is('testAlias'))
            body()
        })

        stepRule.step.dockerExecute(
            script: nullScript,
            dockerImage: 'maven:3.5-jdk-8-alpine',
            sidecarImage: 'selenium/standalone-chrome',
            sidecarName: 'testAlias',
            sidecarReadyCommand: "isReady.sh"
        ) {}

        assertThat(containerCalled, is(true))
        assertThat(shellRule.shell, hasItem("isReady.sh"))
    }

    private class DockerMock {
        private String imageName
        private boolean imagePulled = false
        private int imagePullCount = 0
        private String parameters
        private String sidecarParameters

        DockerMock image(String imageName) {
            this.imageName = imageName
            return this
        }

        void pull() {
            imagePullCount++
            imagePulled = true
        }

        void inside(String parameters, body) {
            this.parameters = parameters
            body()
        }

        void withRun(String parameters, body) {
            this.sidecarParameters = parameters
            body([id: 'uniqueId'])
        }

        String getImageName() {
            return imageName
        }

        boolean isImagePulled() {
            return imagePulled
        }

        String getParameters() {
            return parameters
        }
    }
}
