package com.sonymusic

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

import static org.junit.jupiter.api.Assertions.*

import static org.hamcrest.MatcherAssert.assertThat
import static org.hamcrest.core.StringContains.containsString

class SonarScanTest extends BasePipelineTest {
    def sonarScan

    @BeforeEach
    void setUp() {
        super.setUp()
        sonarScan = loadScript("vars/sonarScan.groovy")

        helper.registerAllowedMethod('sh', [Map.class], { Map args ->
            def script = args.script ?: ''

            if (script.contains('/api/qualitygates/project_status')) {
                return '{"projectStatus":{"status":"OK"}}'
            } else if (script.contains('/api/project_analyses/search')) {
                return '{"analyses":[{"projectVersion":"1.2.3"}]}'
            } else if (script.contains('/api/project_tags/set')) {
                return ''
            } else {
                return ''
            }
        })
        helper.registerAllowedMethod('tool', [String])
        helper.registerAllowedMethod('withSonarQubeEnv', [Map, Closure])
        helper.registerAllowedMethod('nodejs', [Map, Closure])
        helper.registerAllowedMethod('fileExists', [String.class], { String path ->
            return path.endsWith('CODEOWNERS')
        })
        helper.registerAllowedMethod('readFile', [String.class], { String path ->
            return '''
                # Sample CODEOWNERS file
                * @theorchard/default-team
                /lambda/ @theorchard/lambda-team
                /docs/ @theorchard/docs-team
            '''
        })
    }

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

    @Test
    void testCallWithSuccess() {
        // This one should succeed and return nothing
        assertNull(sonarScan(
            project: 'test-project',
            language: 'py',
            projectBaseDir: '.'
        ))

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

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

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

    @Test
    void testGetTeamFromVariousCodeownersSamples() {
        def testCases = [
            [
                desc: "Default team with wildcard first",
                content: '''
                    * @theorchard/default-team
                    /lambda/ @theorchard/lambda-team
                    /docs/ @theorchard/docs-team
                ''',
                expected: 'default-team'
            ],
            [
                desc: "Lambda team first, then default",
                content: '''
                    /lambda/ @theorchard/lambda-team
                    * @theorchard/default-team
                    /docs/ @theorchard/docs-team
                ''',
                expected: 'default-team'
            ],
            [
                desc: "Only one team, no wildcard",
                content: '''
                    /lambda/ @theorchard/lambda-team
                ''',
                expected: 'lambda-team'
            ],
            [
                desc: "Multiple teams, no wildcard, should pick first",
                content: '''
                    /lambda/ @theorchard/lambda-team
                    /docs/ @theorchard/docs-team
                ''',
                expected: 'lambda-team'
            ],
            [
                desc: "No valid orchard team",
                content: '''
                    * @someotherorg/team
                    /docs/ @someorg/docs
                ''',
                expected: null
            ],
            [
                desc: "Empty file",
                content: '',
                expected: null
            ]
        ]

        testCases.each { testCase ->
            helper.registerAllowedMethod('fileExists', [String.class], { true })
            helper.registerAllowedMethod('readFile', [String.class], { testCase.content })

            def team = sonarScan.getTeamFromCodeowners('.')
            assertEquals(testCase.expected, team, "Failed for: ${testCase.desc}")
        }
    }

    @Test
    void testCallWithSubdirectory() {
        assertNull(sonarScan(
            project: 'test-project',
            language: 'py',
            projectBaseDir: 'foobar'
        ))
        def scanShCommand = helper.callStack.find {it.methodName == 'sh' && it.argsToString()?.contains('bin/sonar-scanner')}
        assertThat(scanShCommand.argsToString(), containsString("workspaceDirMocked/foobar "))
    }

    @Test
    void testCallWithAdditionalProperties() {
        assertNull(sonarScan(
            project: 'test-project',
            language: 'java',
            additionalProperties: [
                'sonar.java.binaries': 'target/classes',
                'sonar.verbose': 'true'
            ]
        ))
        def scanShCommand = helper.callStack.find {it.methodName == 'sh' && it.argsToString()?.contains('bin/sonar-scanner')}
        assertThat(scanShCommand.argsToString(), containsString('-Dsonar.java.binaries=target/classes'))
        assertThat(scanShCommand.argsToString(), containsString('-Dsonar.verbose=true'))
    }

    @Test
    void testCallWithAdditionalPropertiesInvalidType() {
        assertThrows(AssertionError.class, {
            sonarScan.call(
                project: 'test-project',
                language: 'py',
                additionalProperties: 'not-a-map'
            )
        })
    }
}
