package com.sonymusic


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

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

class MonorepoUtilsTest extends BaseGlobalVarTest {

    def steps

    @BeforeEach
    void setUp() {
        super.setUp()
        steps = loadScript("test/resources/EmptyPipeline.groovy")
        helper.registerAllowedMethod('checkout', [Map], {
            [BRANCH_NAME: 'main', GIT_PREVIOUS_SUCCESSFUL_COMMIT: 'abc123']
        })
        helper.registerAllowedMethod('lock', [String, Closure])
    }

    @Test
    void testGetInstanceMissingSteps() {
        assertThrowsWithMessage(
            IllegalArgumentException.class,
            "[MonorepoUtils]: 'steps' is not defined.",
            { MonorepoUtils.getInstance(projectsToBuild: ['projectA', 'projectB']) }
        )
    }

    @Test
    void testGetInstance() {
        def instance = MonorepoUtils.getInstance(
            steps: steps,
            projectsToBuild: ['projectA', 'projectB']
        )

        assert(instance instanceof MonorepoUtils)
        assert(instance.projectsToBuild == ['projectA', 'projectB'] as Set)
    }

    @Test
    void testSetupMissingProjectIdentifier() {
        def monorepoUtils = new MonorepoUtils(steps: steps)
        assertThrowsWithMessage(
            IllegalArgumentException.class,
            "[MonorepoUtils]: Either 'projectBasePath' or 'projectConfig' must be provided",
            { monorepoUtils.setup() }
        )
    }

    @Test
    void testSetupWithProjectsToBuildProvided() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectsToBuild: ['projectA', 'projectB'])

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['projectA', 'projectB'] as Set)
        assert(steps.currentBuild.description == "projectA<br>projectB")
    }

    @Test
    void testSetupWithProjectBasePath_NoProjectsFound() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectBasePath: 'lambda')
        helper.registerAllowedMethod('findFiles', [], { [] })

        assertThrowsWithMessage(
            IllegalArgumentException.class,
            "[MonorepoUtils]: No projects found in the specified base path: 'lambda'",
            { monorepoUtils.setup() }
        )
    }

    @Test
    void testSetupWithProjectBasePath_NoChangesFound() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectBasePath: 'lambda')
        helper.registerAllowedMethod('findFiles', [], { [
            [path: 'projectA/', directory: true],
            [path: 'projectB/', directory: true],
            [path: 'file', directory: false],
        ] })
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == [] as Set)
    }

    @Test
    void testSetupWithProjectBasePath_ChangesFound() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectBasePath: 'lambda')
        helper.registerAllowedMethod('findFiles', [], { [
            [path: 'projectA/', directory: true],
            [path: 'projectAA/', directory: true],
            [path: 'projectB/', directory: true],
            [path: 'file', directory: false],
        ] })
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [
            'lambda/projectA/README.md',
            'lambda/file',
        ] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['projectA'] as Set)
    }

    @Test
    void testSetupWithProjectBasePath_Star() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectBasePath: 'lambda', projectsToBuild: ['*'].toSet())
        helper.registerAllowedMethod('findFiles', [], { [
            [path: 'projectA/', directory: true],
            [path: 'projectAA/', directory: true],
            [path: 'projectB/', directory: true],
            [path: 'file', directory: false],
        ] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['projectA', 'projectAA', 'projectB'] as Set)
    }

    @Test
    void testSetupWithExcludedProjects() {
        def monorepoUtils = new MonorepoUtils(
            steps: steps,
            projectBasePath: 'lambda',
            excludedProjects: ['projectB']
        )
        helper.registerAllowedMethod('findFiles', [], { [
            [path: 'projectA/', directory: true],
            [path: 'projectB/', directory: true],
        ] })
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [
            'lambda/projectA/README.md',
            'lambda/projectB/README.md',
        ] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['projectA'] as Set)
    }

    @Test
    void testSetupWithProjectConfigurationMap_NoChangesFound() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectConfig: [
            'projectA': [:],
            'projectB': [:],
        ])
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == [] as Set)
    }

    @Test
    void testSetupWithProjectConfigurationMap_ChangesFound() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectConfig: [
            'projectA': [:],
            'projectB': [:],
        ])
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [
            'projectA/README.md',
        ] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['projectA'] as Set)
    }

    @Test
    void testSetupSortsProjectsToBuild() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectBasePath: 'lambda')
        helper.registerAllowedMethod('findFiles', [], { [
            [path: 'projectB', directory: true],
            [path: 'projectA', directory: true],
        ] })
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [
            'lambda/projectB/README.md',
            'lambda/projectA/README.md',
        ] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['projectA', 'projectB'] as Set)
    }

    @Test
    void testSetupWithRelativePathsDisabled() {
        def monorepoUtils = new MonorepoUtils(
            steps: steps,
            projectBasePath: 'lambda',
            useRelativePaths: false
        )
        helper.registerAllowedMethod('findFiles', [], { [
            [path: 'projectA', directory: true],
        ] })
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [
            'lambda/projectA/README.md',
        ] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['lambda/projectA'] as Set)
    }

    @Test
    void testSetupWithProjectDependencies() {
        def monorepoUtils = new MonorepoUtils(
            steps: steps,
            projectConfig: [
                'projectA': [:],
                'projectB': [:],
            ],
            projectDependencies: [
                'common': ['projectA', 'projectB']
            ]
        )
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [
            'common/README.md',
        ] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['projectA', 'projectB'] as Set)
    }

    @Test
    void testSetupWithProjectDependenciesRegex() {
        def monorepoUtils = new MonorepoUtils(
            steps: steps,
            projectBasePath: 'lambda',
            projectDependencies: [
                'common': [/^lambda\/.*$/]
            ]
        )
        helper.registerAllowedMethod('findFiles', [], { [
            [path: 'projectA', directory: true],
            [path: 'projectB', directory: true],
        ] })
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [
            'common/README.md',
        ] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['projectA', 'projectB'] as Set)
    }

    @Test
    void testSetupRemovesDuplicates() {
        def monorepoUtils = new MonorepoUtils(
            steps: steps,
            projectConfig: [
                'projectA': [:],
            ],
            projectDependencies: [
                'common': ['projectA']
            ]
        )
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [
            'projectA/README.md',
            'common/README.md',
        ] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['projectA'] as Set)
    }

    @Test
    void testSetupNormalisesPaths() {
        def monorepoUtils = new MonorepoUtils(
            steps: steps,
            projectBasePath: '.',
        )
        helper.registerAllowedMethod('findFiles', [], { [
            [path: 'projectA', directory: true],
        ] })
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [
            'projectA/README.md',
        ] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['projectA'] as Set)
    }

    @Test
    void testSetupIgnoresHiddenDirectories() {
        def monorepoUtils = new MonorepoUtils(
            steps: steps,
            projectBasePath: '.',
            projectsToBuild: ['*'] as Set
        )
        helper.registerAllowedMethod('findFiles', [], { [
            [path: '.git', directory: true],
            [path: 'projectA', directory: true],
        ] })

        monorepoUtils.setup()

        assert(monorepoUtils.projectsToBuild == ['projectA'] as Set)
    }

    @Test
    void testWithModifiedProjects_Defaults() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectsToBuild: ['projectA', 'projectB'])
        monorepoUtils.setup()

        def calls = []
        monorepoUtils.withModifiedProjects { project ->
            calls << project
        }

        assertMethodCalledTimes("node", 2)
        assertMethodCalledTimes("checkout", 0)
        assertEquals(["projectA", "projectB"], calls)
    }

    @Test
    void testWithModifiedProjects_ProjectConfigMapProvided() {
        def monorepoUtils = new MonorepoUtils(
            steps: steps,
            projectsToBuild: ['projectA', 'projectB'],
            projectConfig: [
                'projectA': ['prop': 'valueA'],
                'projectB': ['prop': 'valueB'],
            ]
        )
        monorepoUtils.setup()

        def calls = []
        monorepoUtils.withModifiedProjects { project, config ->
            calls << [project: project, config: config]
        }

        assertEquals([
            [project: 'projectA', config: [prop: 'valueA']],
            [project: 'projectB', config: [prop: 'valueB']]
        ], calls)
    }

    @Test
    void testWithModifiedProjects_CheckoutTrue() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectsToBuild: ['projectA', 'projectB'])
        monorepoUtils.setup()

        def calls = []
        monorepoUtils.withModifiedProjects(checkout: true) { project ->
            calls << project
        }

        assertMethodCalledTimes("node", 2)
        assertMethodCalledTimes("checkout", 2)
        assertEquals(["projectA", "projectB"], calls)
    }

    @Test
    void testWithModifiedProjects_CheckoutTrueButAllocateAgentFalse() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectsToBuild: ['projectA', 'projectB'])
        monorepoUtils.setup()

        assertThrowsWithMessage(
            IllegalArgumentException.class,
            "[MonorepoUtils]: 'checkout' cannot be true when 'allocateAgent' is false.",
            { monorepoUtils.withModifiedProjects(checkout: true, allocateAgent: false) {} }
        )
    }

    @Test
    void testWithModifiedProjects_CheckoutFalseAndAllocateAgentFalse() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectsToBuild: ['projectA', 'projectB'])
        monorepoUtils.setup()

        def calls = []
        monorepoUtils.withModifiedProjects(checkout: false, allocateAgent: false) { project ->
            calls << project
        }

        assertMethodCalledTimes("node", 0)
        assertMethodCalledTimes("checkout", 0)
        assertEquals(["projectA", "projectB"], calls)
    }

    @Test
    void testWithModifiedProjects_AgentLabel() {
        def monorepoUtils = new MonorepoUtils(steps: steps, projectsToBuild: ['projectA', 'projectB'])
        monorepoUtils.setup()

        def agentLabelCalls = []
        helper.registerAllowedMethod('node', [String, Closure], { label, closure ->
            agentLabelCalls << label
            closure()
        })

        def calls = []
        monorepoUtils.withModifiedProjects(agentLabel: 'can_reach_orchard_aws_dev') { project ->
            calls << project
        }

        assertMethodCalledTimes("node", 2)
        assertEquals(['can_reach_orchard_aws_dev', 'can_reach_orchard_aws_dev'], agentLabelCalls)
        assertEquals(["projectA", "projectB"], calls)
    }

    @Test
    void testWithModifiedProjects_RelativePathsDisabled() {
        def monorepoUtils = new MonorepoUtils(
            steps: steps,
            projectBasePath: 'lambda',
            useRelativePaths: false,
            projectConfig: [
                'lambda/projectA': ['prop': 'valueA'],
            ]
        )
        helper.registerAllowedMethod('findFiles', [], { [
            [path: 'projectA', directory: true],
        ] })
        helper.registerAllowedMethod('getModifiedPaths', [Map], { [
            'lambda/projectA/README.md',
        ] })
        monorepoUtils.setup()

        def calls = []
        monorepoUtils.withModifiedProjects { project, config ->
            calls << [project: project, config: config]
        }

        assertEquals([
            [project: 'lambda/projectA', config: [prop: 'valueA']],
        ], calls)
    }
}
