package com.sonymusic

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

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

class PlaywrightTestsTest extends BaseGlobalVarTest {
    def playwrightTests

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

        // Register all the Jenkins pipeline steps that the script uses
        helper.registerAllowedMethod('dir', [String, Closure])
        helper.registerAllowedMethod('checkout', [Map])
        helper.registerAllowedMethod('withCredentials', [List, Closure])
        helper.registerAllowedMethod('withEcr', [Closure])
        helper.registerAllowedMethod('withAWS', [Map, Closure])
        helper.registerAllowedMethod('withEnv', [List, Closure])
        helper.registerAllowedMethod('ansiColor', [String, Closure])
        helper.registerAllowedMethod('sh', [String])
        helper.registerAllowedMethod('archiveArtifacts', [Map])
        helper.registerAllowedMethod('publishHTML', [Map])
        helper.registerAllowedMethod('slackNotify', [Map])

        // Mock environment variables
        binding.setVariable('env', [
            GIT_BRANCH: 'master',
            BUILD_TAG: 'jenkins-test-build-123',
            FRAMEWORK_ENV: 'qa',
            JOB_NAME: 'test-job',
            BUILD_NUMBER: '123',
            BUILD_URL: 'http://jenkins.example.com/job/test-job/123/'
        ])

        // Mock currentBuild with startTimeInMillis
        binding.setVariable('currentBuild', [
            startTimeInMillis: System.currentTimeMillis() - 60000 // 1 minute ago
        ])
    }

    @Test
    void testCallWithNoArgs() {
        // Assert error is thrown since required 'tags' parameter is not supplied
        assertThrows(IllegalArgumentException.class, {
            playwrightTests.call(dummy: 'dummy')
        })
    }

    @Test
    void testCallWithMinimalRequiredArgs() {
        playwrightTests(tags: '@smoke')

        // Verify that dir step was called with correct path
        assertMethodCalledOnceWith('dir', 'playwright-tests/default')

        // Verify checkout was called (not PR branch)
        assertMethodCalledTimes('checkout', 1)
        def checkoutArgs = getMethodCallArguments('checkout', 0)
        // The checkout step gets called with a map containing the GitSCM configuration
        assertTrue(checkoutArgs.toString().contains('*/master'))

        // Verify withCredentials was called
        assertMethodCalledTimes('withCredentials', 1)

        // Verify withEcr was called
        assertMethodCalledTimes('withEcr', 1)

        // Verify withAWS was called
        assertMethodCalledTimes('withAWS', 1)
        def awsArgs = getMethodCallArguments('withAWS', 0)
        assertEquals('prod-playwright-tests-role', awsArgs.role.toString())
        assertEquals('437795906767', awsArgs.roleAccount)

        // Verify withEnv was called with correct environment variables
        assertMethodCalledTimes('withEnv', 1)

        // Verify shell commands were executed (split into 2 calls)
        assertMethodCalledTimes('sh', 5)
        def setupShCall = getMethodCallArgumentsAsString('sh', 3)
        assertTrue(setupShCall.contains('rm -rf test-results'))
        assertTrue(setupShCall.contains('docker compose pull framework-runner'))

        def runShCall = getMethodCallArgumentsAsString('sh', 4)
        assertTrue(runShCall.contains('docker compose run --rm framework-runner'))

        // Verify HTML report publishing
        assertMethodCalledTimes('publishHTML', 1)
    }

    @Test
    void testCallWithCustomParameters() {
        playwrightTests(
            tags: '@regression',
            frameworkEnvironment: 'qa',
            runtimeEnvironment: 'fargate',
            configEnvironment: 'staging',
            reportSuffix: 'custom-dir',
            branchName: 'feature/test-branch',
            frameworkRunnerTag: 'v2.0.0',
            githubUser: 'custom-org',
            envVars: [
                CUSTOM_VAR: 'custom-value',
                API_URL: 'https://staging.api.com'
            ]
        )

        // Verify dir with custom report suffix as subdirectory
        assertMethodCalledOnceWith('dir', 'playwright-tests/custom-dir')

        // Verify checkout with custom branch and github user
        def checkoutArgs = getMethodCallArguments('checkout', 0)
        def checkoutStr = checkoutArgs.toString()
        assertTrue(checkoutStr.contains('feature/test-branch'))
        assertTrue(checkoutStr.contains('custom-org'))

        // Verify AWS role uses custom framework environment
        def awsArgs = getMethodCallArguments('withAWS', 0)
        assertEquals('qa-playwright-tests-role', awsArgs.role.toString())
    }

    @Test
    void testCallWithPullRequestBranch() {
        playwrightTests(
            tags: '@smoke',
            branchName: 'PR-123'
        )

        // Verify checkout was called with PR-specific configuration
        def checkoutArgs = getMethodCallArguments('checkout', 0)
        def checkoutStr = checkoutArgs.toString()
        assertTrue(checkoutStr.contains('FETCH_HEAD'))
        assertTrue(checkoutStr.contains('refs/pull/123/head'))
    }

    @Test
    void testEnvironmentVariablesPassedCorrectly() {
        playwrightTests(
            tags: '@api',
            frameworkEnvironment: 'staging',
            runtimeEnvironment: 'ecs',
            configEnvironment: 'dev',
            envVars: [
                DEBUG: 'true',
                TIMEOUT: '30000'
            ]
        )

        // We can't easily inspect withEnv arguments in this test framework,
        // but we can verify it was called the expected number of times
        assertMethodCalledTimes('withEnv', 1)
        assertMethodCalledTimes('withCredentials', 1)
    }

    @Test
    void testReportConfiguration() {
        playwrightTests(tags: '@smoke')

        assertMethodCalledTimes('publishHTML', 1)
    }

    @Test
    void testPublishHTMLConfiguration() {
        playwrightTests(tags: '@smoke')

        assertMethodCalledTimes('publishHTML', 1)
        def htmlArgs = getMethodCallArguments('publishHTML', 0)
        assertEquals('report', htmlArgs.target.reportDir)
        assertEquals('index.html', htmlArgs.target.reportFiles)
        assertEquals('Playwright Report', htmlArgs.target.reportName)
        assertEquals(false, htmlArgs.target.allowMissing)
        assertEquals(true, htmlArgs.target.alwaysLinkToLastBuild)
        assertEquals(true, htmlArgs.target.keepAll)
    }

    @Test
    void testWithCredentialsConfiguration() {
        playwrightTests(tags: '@smoke')

        assertMethodCalledTimes('withCredentials', 1)
    }

    @Test
    void testAnsiColorUsage() {
        playwrightTests(tags: '@smoke')

        assertMethodCalledTimes('ansiColor', 1)
        def ansiColorArgs = getMethodCallArgumentsAsString('ansiColor', 0)
        assertTrue(ansiColorArgs.contains('xterm'))
    }

    @Test
    void testInvalidParameterTypes() {
        // Test with invalid envVars type (should be Map, not List)
        assertThrows(IllegalArgumentException.class, {
            playwrightTests.call(
                tags: '@smoke',
                envVars: ['invalid', 'list', 'format']
            )
        })
    }

    @Test
    void testEmptyEnvVarsMap() {
        // Test that empty envVars map doesn't cause issues
        playwrightTests.call([
            tags: '@smoke',
            envVars: [:]
        ])

        assertMethodCalledTimes('withEnv', 1)
    }

    @Test
    void testDefaultValues() {
        playwrightTests(tags: '@smoke')

        // Verify checkout uses default branch
        def checkoutArgs = getMethodCallArguments('checkout', 0)
        def checkoutStr = checkoutArgs.toString()
        assertTrue(checkoutStr.contains('*/master'))

        // Verify AWS role uses default framework environment
        def awsArgs = getMethodCallArguments('withAWS', 0)
        assertEquals('prod-playwright-tests-role', awsArgs.role.toString())

        // Verify dir uses default checkout subdirectory
        assertMethodCalledOnceWith('dir', 'playwright-tests/default')
    }

    @Test
    void testGithubUserInCheckoutUrl() {
        playwrightTests(
            tags: '@smoke',
            githubUser: 'custom-organization'
        )

        def checkoutArgs = getMethodCallArguments('checkout', 0)
        def checkoutStr = checkoutArgs.toString()
        assertTrue(checkoutStr.contains('custom-organization/playwright-tests.git'))
    }

    @Test
    void testShellCommandsExecuted() {
        playwrightTests(tags: '@smoke')

        assertMethodCalledTimes('sh', 5)

        def firstShCall = getMethodCallArgumentsAsString('sh', 0)
        assertTrue(firstShCall.contains("git log -1 --pretty=format:'%an'"))

        def secondShCall = getMethodCallArgumentsAsString('sh', 1)
        assertTrue(secondShCall.contains("git log -1 --pretty=format:'%ae'"))

        def thirdShCall = getMethodCallArgumentsAsString('sh', 2)
        assertTrue(thirdShCall.contains("git log -1 --pretty=format:'%s'"))

        // Fourth sh call contains clean and pull commands
        def fourthShCall = getMethodCallArgumentsAsString('sh', 3)
        assertTrue(fourthShCall.contains('rm -rf test-results'))
        assertTrue(fourthShCall.contains('docker compose pull framework-runner'))
        assertTrue(fourthShCall.contains('#!/bin/bash -lxe'))

        // Fifth sh call contains run command
        def fifthShCall = getMethodCallArgumentsAsString('sh', 4)
        assertTrue(fifthShCall.contains('docker compose run --rm framework-runner'))
        assertTrue(fifthShCall.contains('#!/bin/bash -lxe'))
    }

    @Test
    void testReportSuffixInReportName() {
        playwrightTests(
            tags: '@smoke',
            reportSuffix: 'mobile'
        )

        assertMethodCalledTimes('publishHTML', 1)
        def htmlArgs = getMethodCallArguments('publishHTML', 0)
        assertEquals('Playwright Report - mobile', htmlArgs.target.reportName.toString())
    }

    @Test
    void testReportSuffixInSubDirectory() {
        playwrightTests(
            tags: '@smoke',
            reportSuffix: 'desktop'
        )

        // Verify dir uses reportSuffix as subdirectory
        assertMethodCalledOnceWith('dir', 'playwright-tests/desktop')
    }

    @Test
    void testReportSuffixDefaultsToEmptyString() {
        playwrightTests(tags: '@smoke')

        // Verify dir uses 'default' when reportSuffix is not provided
        assertMethodCalledOnceWith('dir', 'playwright-tests/default')

        // Verify report name doesn't include suffix
        def htmlArgs = getMethodCallArguments('publishHTML', 0)
        assertEquals('Playwright Report', htmlArgs.target.reportName)
    }

    @Test
    void testReportSuffixPassedAsEnvironmentVariable() {
        playwrightTests(
            tags: '@smoke',
            reportSuffix: 'tablet'
        )

        // Verify withEnv was called (can't easily inspect the exact env vars)
        assertMethodCalledTimes('withEnv', 1)
    }

    @Test
    void testSlackNotificationOnRecovery() {
        // Mock previous build as FAILURE
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(tags: '@smoke')

        // Verify slackNotify was called once for recovery
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertEquals('#e2e-test-results', slackArgs.channel)
        assertTrue(slackArgs.message.toString().contains('SUCCESS'))
    }

    @Test
    void testSlackNotificationOnRegression() {
        // Mock previous build as SUCCESS
        def previousBuild = [result: 'SUCCESS']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        // Mock sh to throw exception on second call (test failure)
        def shCallCount = 0
        helper.registerAllowedMethod('sh', [String], {
            shCallCount++
            if (shCallCount == 2) {
                throw new Exception('Test failure')
            }
        })

        try {
            playwrightTests(tags: '@smoke')
        } catch (Exception e) {
            // Expected exception from test failure
        }

        // Verify slackNotify was called once for regression
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertEquals('#e2e-test-results', slackArgs.channel)
        assertTrue(slackArgs.message.toString().contains('FAILURE'))
    }

    @Test
    void testSlackNotificationWithReportSuffix() {
        // Mock previous build as FAILURE
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(
            tags: '@smoke',
            reportSuffix: 'mobile'
        )

        // Verify slackNotify was called with reportSuffix in message
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertTrue(slackArgs.message.toString().contains('Playwright Tests - mobile'))
    }

    @Test
    void testSlackNotificationWithAdditionalChannel() {
        // Mock previous build as FAILURE
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(
            tags: '@smoke',
            slackNotificationChannels: ['#team-channel']
        )

        // Verify slackNotify was called twice (default + additional channel)
        assertMethodCalledTimes('slackNotify', 2)
        def slackArgs1 = getMethodCallArguments('slackNotify', 0)
        def slackArgs2 = getMethodCallArguments('slackNotify', 1)

        def channels = [slackArgs1.channel, slackArgs2.channel]
        assertTrue(channels.contains('#e2e-test-results'))
        assertTrue(channels.contains('#team-channel'))
    }

    @Test
    void testSlackNotificationWithMultipleAdditionalChannels() {
        // Mock previous build as FAILURE
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(
            tags: '@smoke',
            slackNotificationChannels: ['#team-channel', '#qa-notifications', '#alerts']
        )

        // Verify slackNotify was called 4 times (default + 3 additional channels)
        assertMethodCalledTimes('slackNotify', 4)
        def slackArgs1 = getMethodCallArguments('slackNotify', 0)
        def slackArgs2 = getMethodCallArguments('slackNotify', 1)
        def slackArgs3 = getMethodCallArguments('slackNotify', 2)
        def slackArgs4 = getMethodCallArguments('slackNotify', 3)

        def channels = [slackArgs1.channel, slackArgs2.channel, slackArgs3.channel, slackArgs4.channel]
        assertTrue(channels.contains('#e2e-test-results'))
        assertTrue(channels.contains('#team-channel'))
        assertTrue(channels.contains('#qa-notifications'))
        assertTrue(channels.contains('#alerts'))
    }

    @Test
    void testNoSlackNotificationWhenNoStateChange() {
        // Add readFile mock
        helper.registerAllowedMethod('readFile', [String], { String path ->
            return '<html><body><a href="http://report.com">View Test Results</a></body></html>'
        })

        // Mock previous build as SUCCESS, current also SUCCESS
        def previousBuild = [result: 'SUCCESS']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(tags: '@smoke')

        // Verify slackNotify was not called (no state change)
        assertMethodCalledTimes('slackNotify', 0)
    }

    @Test
    void testSlackNotificationOnFirstRun() {
        // Mock no previous build (first run)
        binding.setVariable('currentBuild', [
            previousBuild: null,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(tags: '@smoke')

        // Verify slackNotify was not called on successful first run
        assertMethodCalledTimes('slackNotify', 0)
    }

    @Test
    void testSlackNotificationOnFirstRunFailure() {
        // Mock no previous build (first run)
        binding.setVariable('currentBuild', [
            previousBuild: null,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        // Mock sh to throw exception on second call (test failure)
        def shCallCount = 0
        helper.registerAllowedMethod('sh', [String], {
            shCallCount++
            if (shCallCount == 2) {
                throw new Exception('Test failure')
            }
        })

        try {
            playwrightTests(tags: '@smoke')
        } catch (Exception e) {
            // Expected exception from test failure
        }

        // Verify slackNotify was called for first run failure
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertTrue(slackArgs.message.toString().contains('FAILURE'))
    }

    @Test
    void testPlaywrightLinkExtractedFromHTML() {
        // Mock readFile to return HTML with Playwright link
        helper.registerAllowedMethod('readFile', [String], { String path ->
            if (path == 'report/index.html') {
                return '''<!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Playwright Test Results</title>
    </head>
    <body>
      <h1>Playwright Test Results</h1>
      <p>Open the link below in a new tab to view the test results:</p>
      <a href="https://qa-playwright.theorchard.io/api/bucket/qa/report/playwright-tests-PR-1-5/merged-report" target="_blank" rel="noopener noreferrer">View Test Results</a>
    </body>
    </html>'''
            }
            return ''
        })

        // Mock previous build as FAILURE to trigger Slack notification
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(tags: '@smoke')

        // Verify readFile was called
        assertMethodCalledOnceWith('readFile', 'report/index.html')

        // Verify Slack notification contains the extracted link
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertTrue(slackArgs.message.toString().contains('https://qa-playwright.theorchard.io/api/bucket/qa/report/playwright-tests-PR-1-5/merged-report'))
        assertTrue(slackArgs.message.toString().contains('Open Playwright Report'))
    }

    @Test
    void testSlackNotificationWithoutPlaywrightLinkWhenFileNotFound() {
        // Mock readFile to throw exception (file not found)
        helper.registerAllowedMethod('readFile', [String], { String path ->
            throw new Exception('File not found')
        })

        // Mock previous build as FAILURE to trigger Slack notification
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(tags: '@smoke')

        // Verify Slack notification contains fallback message
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertTrue(slackArgs.message.toString().contains('Playwright Report not available'))
        assertFalse(slackArgs.message.toString().contains('Open Playwright Report'))
    }

    @Test
    void testSlackNotificationWithoutPlaywrightLinkWhenNoHrefFound() {
        // Mock readFile to return HTML without href
        helper.registerAllowedMethod('readFile', [String], { String path ->
            if (path == 'report/index.html') {
                return '<html><body><p>No links here</p></body></html>'
            }
            return ''
        })

        // Mock previous build as FAILURE to trigger Slack notification
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(tags: '@smoke')

        // Verify Slack notification contains fallback message
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertTrue(slackArgs.message.toString().contains('Playwright Report not available'))
    }

    @Test
    void testSlackNotificationColorOnRecovery() {
        // Mock readFile to return valid HTML
        helper.registerAllowedMethod('readFile', [String], { String path ->
            if (path == 'report/index.html') {
                return '<html><body><a href="http://report.com">View Test Results</a></body></html>'
            }
            return ''
        })

        // Mock previous build as FAILURE
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(tags: '@smoke')

        // Verify Slack notification uses 'good' color for recovery
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertEquals('good', slackArgs.color)
    }

    @Test
    void testSlackNotificationColorOnRegression() {
        // Mock readFile to return valid HTML
        helper.registerAllowedMethod('readFile', [String], { String path ->
            if (path == 'report/index.html') {
                return '<html><body><a href="http://report.com">View Test Results</a></body></html>'
            }
            return ''
        })

        // Mock previous build as SUCCESS
        def previousBuild = [result: 'SUCCESS']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        // Mock sh to throw exception on second call (test failure)
        def shCallCount = 0
        helper.registerAllowedMethod('sh', [String], {
            shCallCount++
            if (shCallCount == 2) {
                throw new Exception('Test failure')
            }
        })

        try {
            playwrightTests(tags: '@smoke')
        } catch (Exception e) {
            // Expected exception from test failure
        }

        // Verify Slack notification uses 'danger' color for regression
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertEquals('danger', slackArgs.color)
    }

    @Test
    void testSlackNotificationIncludesJenkinsUrl() {
        // Mock readFile to return valid HTML
        helper.registerAllowedMethod('readFile', [String], { String path ->
            if (path == 'report/index.html') {
                return '<html><body><a href="http://report.com">View Test Results</a></body></html>'
            }
            return ''
        })

        // Mock previous build as FAILURE
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(tags: '@smoke')

        // Verify Slack notification includes Jenkins build URL
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertTrue(slackArgs.message.toString().contains('http://jenkins.example.com/job/test-job/123/'))
        assertTrue(slackArgs.message.toString().contains('Open Jenkins'))
    }

    @Test
    void testSlackNotificationOnProdFailure() {
        // Mock readFile to return valid HTML
        helper.registerAllowedMethod('readFile', [String], { String path ->
            if (path == 'report/index.html') {
                return '<html><body><a href="http://report.com">View Test Results</a></body></html>'
            }
            return ''
        })

        // Mock previous build as SUCCESS (so normally no slack message would be sent)
        def previousBuild = [result: 'SUCCESS']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        // Mock sh to throw exception on second call (test failure)
        def shCallCount = 0
        helper.registerAllowedMethod('sh', [String], {
            shCallCount++
            if (shCallCount == 2) {
                throw new Exception('Test failure')
            }
        })

        try {
            playwrightTests(
                tags: '@smoke',
                configEnvironment: 'prod'  // This should trigger slack message even if previous was SUCCESS
            )
        } catch (Exception e) {
            // Expected exception from test failure
        }

        // Verify slackNotify was called for prod failure
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertEquals('#e2e-test-results', slackArgs.channel)
        assertTrue(slackArgs.message.toString().contains('FAILURE'))
        assertEquals('danger', slackArgs.color)
    }

    @Test
    void testSlackNotificationOnProdFailureWithMultipleChannels() {
        // Mock readFile to return valid HTML
        helper.registerAllowedMethod('readFile', [String], { String path ->
            if (path == 'report/index.html') {
                return '<html><body><a href="http://report.com">View Test Results</a></body></html>'
            }
            return ''
        })

        // Mock previous build as SUCCESS
        def previousBuild = [result: 'SUCCESS']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        // Mock sh to throw exception on second call (test failure)
        def shCallCount = 0
        helper.registerAllowedMethod('sh', [String], {
            shCallCount++
            if (shCallCount == 2) {
                throw new Exception('Test failure')
            }
        })

        try {
            playwrightTests(
                tags: '@smoke',
                configEnvironment: 'prod',
                slackNotificationChannels: ['#prod-alerts', '#critical-failures']
            )
        } catch (Exception e) {
            // Expected exception from test failure
        }

        // Verify slackNotify was called for all channels (1 default + 2 additional)
        assertMethodCalledTimes('slackNotify', 3)
        def slackArgs1 = getMethodCallArguments('slackNotify', 0)
        def slackArgs2 = getMethodCallArguments('slackNotify', 1)
        def slackArgs3 = getMethodCallArguments('slackNotify', 2)

        def channels = [slackArgs1.channel, slackArgs2.channel, slackArgs3.channel]
        assertTrue(channels.contains('#e2e-test-results'))
        assertTrue(channels.contains('#prod-alerts'))
        assertTrue(channels.contains('#critical-failures'))
    }

    @Test
    void testNoSlackNotificationOnProdSuccess() {
        // Mock readFile to return valid HTML
        helper.registerAllowedMethod('readFile', [String], { String path ->
            if (path == 'report/index.html') {
                return '<html><body><a href="http://report.com">View Test Results</a></body></html>'
            }
            return ''
        })

        // Mock previous build as SUCCESS
        def previousBuild = [result: 'SUCCESS']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(
            tags: '@smoke',
            configEnvironment: 'prod'
        )

        // Verify no slack notification on prod success with no state change
        assertMethodCalledTimes('slackNotify', 0)
    }

    @Test
    void testNoSlackNotificationOnQaFailureWithoutStateChange() {
        // Mock readFile to return valid HTML
        helper.registerAllowedMethod('readFile', [String], { String path ->
            if (path == 'report/index.html') {
                return '<html><body><a href="http://report.com">View Test Results</a></body></html>'
            }
            return ''
        })

        // Mock previous build as FAILURE (so current failure is not a state change)
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        // Mock sh to throw exception on second call (test failure)
        def shCallCount = 0
        helper.registerAllowedMethod('sh', [String], {
            shCallCount++
            if (shCallCount == 2) {
                throw new Exception('Test failure')
            }
        })

        try {
            playwrightTests(
                tags: '@smoke',
                configEnvironment: 'qa'  // Not prod, and no state change (was FAILURE, now FAILURE)
            )
        } catch (Exception e) {
            // Expected exception from test failure
        }

        // Verify no slack notification since config is not prod and no state change
        assertMethodCalledTimes('slackNotify', 0)
    }

    @Test
    void testGithubPRCommentPostedOnSuccess() {
        helper.registerAllowedMethod('githubPostPrComment', [Map])
        helper.registerAllowedMethod('readFile', [String], { String path ->
            return '<html><body><a href="https://playwright.example.com/report">Report</a><a href="https://datadog.example.com/dashboard">Dashboard</a></body></html>'
        })

        binding.setVariable('currentBuild', [
            previousBuild: [result: 'SUCCESS'],
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(
            tags: '@smoke',
            githubPRRepo: 'my-repo',
            githubPRID: '42'
        )

        assertMethodCalledTimes('githubPostPrComment', 1)
        def args = getMethodCallArguments('githubPostPrComment', 0)
        assertEquals('my-repo', args.repo)
        assertEquals('42', args.pullRequestId)
        assertTrue(args.message.toString().contains('SUCCESS'))
        assertTrue(args.message.toString().contains('@smoke'))
        assertTrue(args.message.toString().contains('https://playwright.example.com/report'))
        assertTrue(args.message.toString().contains('https://datadog.example.com/dashboard'))
    }

    @Test
    void testGithubPRCommentPostedOnFailure() {
        helper.registerAllowedMethod('githubPostPrComment', [Map])
        helper.registerAllowedMethod('readFile', [String], { String path ->
            return '<html><body><a href="https://playwright.example.com/report">Report</a></body></html>'
        })

        binding.setVariable('currentBuild', [
            previousBuild: [result: 'SUCCESS'],
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        helper.registerAllowedMethod('sh', [String], { String cmd ->
            if (cmd.contains('docker compose run')) {
                throw new Exception('Test failure')
            }
        })

        try {
            playwrightTests(
                tags: '@regression',
                githubPRRepo: 'my-repo',
                githubPRID: '99'
            )
        } catch (Exception e) {
            // Expected
        }

        assertMethodCalledTimes('githubPostPrComment', 1)
        def args = getMethodCallArguments('githubPostPrComment', 0)
        assertTrue(args.message.toString().contains('FAILURE'))
        assertTrue(args.message.toString().contains('@regression'))
    }

    @Test
    void testGithubPRCommentNotPostedWhenParamsMissing() {
        helper.registerAllowedMethod('githubPostPrComment', [Map])

        binding.setVariable('currentBuild', [
            previousBuild: [result: 'SUCCESS'],
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        // Neither param provided
        playwrightTests(tags: '@smoke')
        assertMethodNotCalled('githubPostPrComment')
    }

    @Test
    void testGithubPRCommentNotPostedWhenOnlyRepoProvided() {
        helper.registerAllowedMethod('githubPostPrComment', [Map])

        binding.setVariable('currentBuild', [
            previousBuild: [result: 'SUCCESS'],
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(tags: '@smoke', githubPRRepo: 'my-repo')
        assertMethodNotCalled('githubPostPrComment')
    }

    @Test
    void testGithubPRCommentNotPostedWhenOnlyIDProvided() {
        helper.registerAllowedMethod('githubPostPrComment', [Map])

        binding.setVariable('currentBuild', [
            previousBuild: [result: 'SUCCESS'],
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(tags: '@smoke', githubPRID: '42')
        assertMethodNotCalled('githubPostPrComment')
    }

    @Test
    void testGithubPRCommentWithoutLinks() {
        helper.registerAllowedMethod('githubPostPrComment', [Map])
        // readFile throws - no report file available
        helper.registerAllowedMethod('readFile', [String], { String path ->
            throw new Exception('File not found')
        })

        binding.setVariable('currentBuild', [
            previousBuild: [result: 'SUCCESS'],
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(
            tags: '@smoke',
            githubPRRepo: 'my-repo',
            githubPRID: '42'
        )

        assertMethodCalledTimes('githubPostPrComment', 1)
        def args = getMethodCallArguments('githubPostPrComment', 0)
        assertTrue(args.message.toString().contains('SUCCESS'))
        assertTrue(args.message.toString().contains('Pipeline:'))
        assertFalse(args.message.toString().contains('Playwright Report:'))
        assertFalse(args.message.toString().contains('Datadog Test Run:'))
    }

    @Test
    void testGithubPRCommentWithReportSuffix() {
        helper.registerAllowedMethod('githubPostPrComment', [Map])

        binding.setVariable('currentBuild', [
            previousBuild: [result: 'SUCCESS'],
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        playwrightTests(
            tags: '@smoke',
            reportSuffix: 'mobile',
            githubPRRepo: 'my-repo',
            githubPRID: '42'
        )

        assertMethodCalledTimes('githubPostPrComment', 1)
        def args = getMethodCallArguments('githubPostPrComment', 0)
        assertTrue(args.message.toString().contains('Playwright Tests - mobile'))
    }

    @Test
    void testSlackNotificationOnProdFailureWhenPreviousAlsoFailed() {
        // Mock readFile to return valid HTML
        helper.registerAllowedMethod('readFile', [String], { String path ->
            if (path == 'report/index.html') {
                return '<html><body><a href="http://report.com">View Test Results</a></body></html>'
            }
            return ''
        })

        // Mock previous build as FAILURE (so normally no slack message would be sent due to no state change)
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])

        // Mock sh to throw exception on second call (test failure)
        def shCallCount = 0
        helper.registerAllowedMethod('sh', [String], {
            shCallCount++
            if (shCallCount == 2) {
                throw new Exception('Test failure')
            }
        })

        try {
            playwrightTests(
                tags: '@smoke',
                configEnvironment: 'prod'  // This should trigger slack message even though previous was also FAILURE
            )
        } catch (Exception e) {
            // Expected exception from test failure
        }

        // Verify slackNotify was called for prod failure even when previous build also failed
        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertEquals('#e2e-test-results', slackArgs.channel)
        assertTrue(slackArgs.message.toString().contains('FAILURE'))
        assertEquals('danger', slackArgs.color)
    }

    @Test
    void testSlackNotificationUsesPRChannelWhenPR() {
        def previousBuild = [result: 'FAILURE']
        binding.setVariable('currentBuild', [
            previousBuild: previousBuild,
            currentResult: 'SUCCESS',
            startTimeInMillis: System.currentTimeMillis() - 60000
        ])
        // Set CHANGE_ID to simulate a PR build
        binding.setVariable('env', [
            GIT_BRANCH: 'master',
            BUILD_TAG: 'jenkins-test-build-123',
            FRAMEWORK_ENV: 'qa',
            JOB_NAME: 'test-job',
            BUILD_NUMBER: '123',
            BUILD_URL: 'http://jenkins.example.com/job/test-job/123/',
            CHANGE_ID: '456'
        ])

        playwrightTests(tags: '@smoke')

        assertMethodCalledTimes('slackNotify', 1)
        def slackArgs = getMethodCallArguments('slackNotify', 0)
        assertEquals('#playwright-tests-e2e-results', slackArgs.channel)
    }

}
