package com.sonymusic

import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

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

class GithubVulnerabilityScanTest extends BaseGlobalVarTest {
    def githubVulnerabilityScan

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

        // Reset environment variables
        binding.setVariable('env', [
            GIT_URL    : 'https://github.com/theorchard/ows-pdp.git',
            GIT_COMMIT : 'abcdef',
            BUILD_URL  : 'https://pipeline.theorchard.io/job/theorchard/job/ows-pdp/job/PR-1/1/'
        ])

        // withEcr should execute its body
        helper.registerAllowedMethod('withEcr', [Map, Closure]) { Map m, Closure c -> c() }

        // Stub credential binding steps used inside the library
        helper.registerAllowedMethod('file', [Map]) { Map m -> m }

        // Make withCredentials set env vars expected by the pipeline step
        helper.registerAllowedMethod('withCredentials', [List, Closure]) { List bindings, Closure body ->
            bindings.each { b ->
                if (b instanceof Map && b.variable) {
                    if (b.credentialsId == 'security-scripts-pde-github-api-key' && b.variable == 'GITHUB_PRIVATE_KEY_FILE') {
                        binding.getVariable('env')['GITHUB_PRIVATE_KEY_FILE'] = '/tmp/fake_github_private_key.pem'
                    }
                }
            }
            body()
        }

        // Stub sh(Map) for uid/gid lookups
        helper.registerAllowedMethod('sh', [Map]) { Map args ->
            def script = args.script?.toString()
            if (args.returnStdout) {
                if (script == 'id -u') return "1000\n"
                if (script == 'id -g') return "1000\n"
                return "\n"
            }
            return null
        }

        if (!helper.allowedMethodCallbacks.containsKey('sh(java.lang.String)')) {
            helper.registerAllowedMethod('sh', [String]) { String s -> null }
        }
    }

    private String getDockerRunCommand() {
        def shCalls = helper.callStack
            .findAll { it.methodName == 'sh' }
            .collect { it.argsToString() }
        return shCalls.findAll { it.contains('docker run') }.last()
    }

    @Test
    void testCallNonPullRequestBuild() {
        assertThrowsWithMessage(
            AssertionError.class,
            "CHANGE_ID environment variable is not set. GitHub vulnerability scan can only be run in PR builds.",
            {
                githubVulnerabilityScan.call()
            }
        )
    }

    @Test
    void testCallPullRequestBuild() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: 'skip vulnerability scan'
        ]

        githubVulnerabilityScan()

        assertMethodCalledOnceWith('withEcr', '{registries=[{accountId=086679231553, region=us-east-1}]}')

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('docker run --rm')
        assert dockerRun.contains('--user 1000:1000')
        assert dockerRun.contains('-v "$(pwd):/workspace:ro"')
        assert dockerRun.contains('-v "${GITHUB_PRIVATE_KEY_FILE}:/run/secrets/github_private_key:ro"')
        assert dockerRun.contains('-e GITHUB_PRIVATE_KEY_PATH="/run/secrets/github_private_key"')
        assert dockerRun.contains('-e GITHUB_APP_ID="3600985"')
        assert dockerRun.contains('-e GITHUB_INSTALLATION_ID="129508735"')
        assert dockerRun.contains('-e GITHUB_REPO_OWNER="theorchard"')
        assert dockerRun.contains('-e GITHUB_REPO="ows-pdp"')
        assert dockerRun.contains('-e BASE_PATH="/workspace"')
        assert dockerRun.contains('-e COMMIT_SHA="abcdef"')
        assert dockerRun.contains('-e BUILD_URL="https://pipeline.theorchard.io/job/theorchard/job/ows-pdp/job/PR-1/1/"')
        assert dockerRun.contains('-e GITHUB_PR_NUM="1"')
        assert dockerRun.contains('-e GITHUB_PR_COMMENT="skip vulnerability scan"')
        assertFalse(dockerRun.contains('GITHUB_TOKEN'))
        assert dockerRun.contains('--post-comment')
    }

    @Test
    void testMountsWorkspaceReadOnly() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan()

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('/workspace:ro')
        assert !dockerRun.contains('/repository')
    }

    @Test
    void testAutoDetectsRepoOwnerFromGitUrl() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan()

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('-e GITHUB_REPO_OWNER="theorchard"')
        assert dockerRun.contains('-e GITHUB_REPO="ows-pdp"')
    }

    @Test
    void testCustomRepoOwner() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan(repoOwner: 'custom-org')

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('-e GITHUB_REPO_OWNER="custom-org"')
    }

    @Test
    void testWithProjectDir() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan(projectDir: 'lambda/snapshot_contracts')

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('-e PROJECT_DIR="lambda/snapshot_contracts"')
    }

    @Test
    void testWithExcludedPath() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan(excludedPath: 'vagrant,tests')

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('-e EXCLUDED_PATH="sonar-scanner-temp,dockerToEcr,vagrant,tests"')
    }

    @Test
    void testWithUxTable() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan(ux: 'table')

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('--post-comment --ux table')
    }

    @Test
    void testWithUxMarkdown() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan(ux: 'markdown')

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('--post-comment --ux markdown')
    }

    @Test
    void testUxRichRejected() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        assertThrowsWithMessage(
            IllegalArgumentException.class,
            "--ux rich is not compatible with --post-comment. Use --ux table (default) or --ux markdown.",
            {
                githubVulnerabilityScan.call(ux: 'rich')
            }
        )
    }

    @Test
    void testDefaultUxOmitsFlag() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan()

        def dockerRun = getDockerRunCommand()
        assert !dockerRun.contains('--ux') : "Default should not include --ux flag"
    }

    @Test
    void testWithLogLevel() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan(logLevel: 'debug')

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('-e LOG_LEVEL="debug"')
        assert dockerRun.contains('--log-level debug')
    }

    @Test
    void testDefaultLogLevelOmitsFlag() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan()

        def dockerRun = getDockerRunCommand()
        assert !dockerRun.contains('LOG_LEVEL') : "Default should not include LOG_LEVEL env var"
        assert !dockerRun.contains('--log-level') : "Default should not include --log-level flag"
    }

    @Test
    void testWithSkipPhrase() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan(skipPhrase: 'no scan please')

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('--skip-phrase "no scan please"')
    }

    @Test
    void testWithAllOptions() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: 'no scan please'
        ]

        githubVulnerabilityScan(
            projectDir: 'lambda/snapshot_contracts',
            repoOwner: 'theorchard',
            excludedPath: 'vagrant',
            skipPhrase: 'no scan please',
            ux: 'table',
            logLevel: 'debug'
        )

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('docker run --rm')
        assert dockerRun.contains('--user 1000:1000')
        assert dockerRun.contains('/workspace:ro')
        assert dockerRun.contains('-e GITHUB_APP_ID="3600985"')
        assert dockerRun.contains('-e GITHUB_INSTALLATION_ID="129508735"')
        assert dockerRun.contains('-e GITHUB_PRIVATE_KEY_PATH="/run/secrets/github_private_key"')
        assert dockerRun.contains(':/run/secrets/github_private_key:ro')
        assert dockerRun.contains('-e GITHUB_REPO_OWNER="theorchard"')
        assert dockerRun.contains('-e GITHUB_REPO="ows-pdp"')
        assert dockerRun.contains('-e BASE_PATH="/workspace"')
        assert dockerRun.contains('-e PROJECT_DIR="lambda/snapshot_contracts"')
        assert dockerRun.contains('-e EXCLUDED_PATH="sonar-scanner-temp,dockerToEcr,vagrant"')
        assert dockerRun.contains('-e LOG_LEVEL="debug"')
        assert dockerRun.contains('-e GITHUB_PR_COMMENT="no scan please"')
        assert dockerRun.contains('--post-comment --ux table --skip-phrase "no scan please" --log-level debug')
        assert !dockerRun.contains('GITHUB_TOKEN')
    }

    @Test
    void testCustomImage() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan(scannerImage: '0123456789012.dkr.ecr.us-west-2.amazonaws.com/custom-v2:latest')

        assertMethodCalledOnceWith('withEcr', '{registries=[{accountId=0123456789012, region=us-west-2}]}')

        def allSh = helper.callStack
            .findAll { it.methodName == 'sh' }
            .collect { it.argsToString() }
        assert allSh.any { it.contains('docker pull 0123456789012.dkr.ecr.us-west-2.amazonaws.com/custom-v2:latest') }
    }

    @Test
    void testDefaultPostCommentFlag() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan()

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('--post-comment')
    }

    @Test
    void testEmptyPrCommentDefaultsToEmptyString() {
        binding.setVariable('env', [
            GIT_URL       : 'https://github.com/theorchard/ows-pdp.git',
            GIT_COMMIT    : 'abcdef',
            BUILD_URL     : 'https://pipeline.theorchard.io/job/theorchard/job/ows-pdp/job/PR-1/1/',
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1'
        ])

        githubVulnerabilityScan()

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('-e GITHUB_PR_COMMENT=""')
    }

    @Test
    void testDoesNotIncludeGithubUser() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan()

        def dockerRun = getDockerRunCommand()
        assert !dockerRun.contains('GITHUB_USER') : "Scanner should not pass GITHUB_USER but found: ${dockerRun}"
    }

    @Test
    void testGracePeriodEnvVarsNeverPresent() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan()

        def dockerRun = getDockerRunCommand()
        assert !dockerRun.contains('CRITICAL_ERROR_DAYS') : "Scanner should not contain CRITICAL_ERROR_DAYS"
        assert !dockerRun.contains('HIGH_ERROR_DAYS') : "Scanner should not contain HIGH_ERROR_DAYS"
        assert !dockerRun.contains('MODERATE_ERROR_DAYS') : "Scanner should not contain MODERATE_ERROR_DAYS"
        assert !dockerRun.contains('LOW_ERROR_DAYS') : "Scanner should not contain LOW_ERROR_DAYS"
    }

    @Test
    void testDefaultExcludedPath() {
        binding.getVariable('env') << [
            CHANGE_AUTHOR : 'jsmith',
            CHANGE_ID     : '1',
            GITHUB_COMMENT: ''
        ]

        githubVulnerabilityScan()

        def dockerRun = getDockerRunCommand()
        assert dockerRun.contains('-e EXCLUDED_PATH="sonar-scanner-temp,dockerToEcr"')
    }
}
