/*
 * Copyright 2013-2026, Seqera Labs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.seqera.tower.plugin.auth

import io.seqera.tower.plugin.TowerClient
import nextflow.Const
import nextflow.SysEnv
import nextflow.util.ColorUtil
import org.junit.Rule
import spock.lang.Specification
import spock.lang.TempDir
import test.OutputCapture

import java.nio.file.Files
import java.nio.file.Path

/**
 * Test CmdAuth functionality
 *
 * @author Phil Ewels <phil.ewels@seqera.io>
 */
class AuthCommandImplTest extends Specification {

    @Rule
    OutputCapture capture = new OutputCapture()

    @TempDir
    Path tempDir


    def 'should read config correctly'() {
        given:
        def cmd = new AuthCommandImpl()

        expect:
        // readConfig method should return a Map
        cmd.readConfig() instanceof Map
    }

    def 'should handle config writing'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def config = [
            'tower.accessToken': 'test-token',
            'tower.enabled'    : true
        ]
        def authFile = tempDir.resolve('seqera-auth.config')
        def configFile = tempDir.resolve('config')
        cmd.getAuthFile() >> authFile
        cmd.getConfigFile() >> configFile
        when:
        cmd.writeConfig(config, null, null)

        then:
        noExceptionThrown()
    }


    def 'should normalize API URL correctly'() {
        given:
        def cmd = new AuthCommandImpl()

        expect:
        cmd.normalizeApiUrl(null) == 'https://api.cloud.seqera.io'
        cmd.normalizeApiUrl('') == 'https://api.cloud.seqera.io'
        cmd.normalizeApiUrl('https://example.com') == 'https://example.com'
        cmd.normalizeApiUrl('http://example.com') == 'http://example.com'
        cmd.normalizeApiUrl('example.com') == 'https://example.com'
        cmd.normalizeApiUrl('api.cloud.seqera.io') == 'https://api.cloud.seqera.io'
    }

    def 'should decode token ID correctly'() {
        given:
        def cmd = new AuthCommandImpl()
        def tokenData = '{"tid":"token-123","exp":1234567890}'
        def encodedToken = Base64.encoder.encodeToString(tokenData.bytes)

        when:
        def tokenId = cmd.decodeTokenId(encodedToken)

        then:
        tokenId == 'token-123'
    }

    def 'should fail to decode token without ID'() {
        given:
        def cmd = new AuthCommandImpl()
        def tokenData = '{"exp":1234567890}'
        def encodedToken = Base64.encoder.encodeToString(tokenData.bytes)

        when:
        cmd.decodeTokenId(encodedToken)

        then:
        thrown(RuntimeException)
    }

    def 'should fail to decode invalid token'() {
        given:
        def cmd = new AuthCommandImpl()
        def invalidToken = 'not-a-valid-base64-token'

        when:
        cmd.decodeTokenId(invalidToken)

        then:
        thrown(RuntimeException)
    }

    def 'should remove includeConfig line correctly'() {
        given:
        def cmd = new AuthCommandImpl()
        def content = """
// Some config
param1 = 'value1'

includeConfig 'seqera-auth.config'

param2 = 'value2'
"""

        when:
        def result = cmd.removeIncludeConfigLine(content)

        then:
        !result.contains("includeConfig 'seqera-auth.config'")
        result.contains('param1 = \'value1\'')
        result.contains('param2 = \'value2\'')
    }

    def 'should handle content without includeConfig line'() {
        given:
        def cmd = new AuthCommandImpl()
        def content = """
// Some config
param1 = 'value1'
param2 = 'value2'"""

        when:
        def result = cmd.removeIncludeConfigLine(content)

        then:
        result == content
    }

    def 'should get config file path'() {
        given:
        def cmd = new AuthCommandImpl()

        when:
        def configFile = cmd.getConfigFile()

        then:
        configFile == Const.APP_HOME_DIR.resolve('config')
    }

    def 'should get auth file path'() {
        given:
        def cmd = new AuthCommandImpl()

        when:
        def authFile = cmd.getAuthFile()

        then:
        authFile == Const.APP_HOME_DIR.resolve('seqera-auth.config')
    }

    def 'should read empty auth file'() {
        given:
        def cmd = new AuthCommandImpl()

        when:
        def config = cmd.readAuthFile()

        then:
        config instanceof Map
        config.isEmpty() || config.size() >= 0
    }

    def 'should write config to seqera-auth.config file'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def authFile = tempDir.resolve('seqera-auth.config')
        def configFile = tempDir.resolve('config')

        cmd.getAuthFile() >> authFile
        cmd.getConfigFile() >> configFile

        def config = [
            'tower.accessToken': 'test-token-123',
            'tower.endpoint'   : 'https://api.cloud.seqera.io',
            'tower.enabled'    : true
        ]

        when:
        cmd.writeConfig(config, null)

        then:
        Files.exists(authFile)
        def authContent = Files.readString(authFile)
        authContent.contains('accessToken = \'test-token-123\'')
        authContent.contains('endpoint = \'https://api.cloud.seqera.io\'')
        authContent.contains('enabled = true')
    }

    def 'should write config with workspace metadata'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def authFile = tempDir.resolve('seqera-auth.config')
        def configFile = tempDir.resolve('config')

        cmd.getAuthFile() >> authFile
        cmd.getConfigFile() >> configFile

        def config = [
            'tower.accessToken': 'test-token-123',
            'tower.endpoint'   : 'https://api.cloud.seqera.io',
            'tower.workspaceId': '12345'
        ]
        def metadata = [
            orgName          : 'TestOrg',
            workspaceName    : 'TestWorkspace',
            workspaceFullName: 'test-org/test-workspace'
        ]

        when:
        cmd.writeConfig(config, metadata)

        then:
        Files.exists(authFile)
        def authContent = Files.readString(authFile)
        authContent.contains('workspaceId = \'12345\'')
        authContent.contains('// TestOrg / TestWorkspace [test-org/test-workspace]')
    }

    def 'should add includeConfig line to main config file'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def authFile = tempDir.resolve('seqera-auth.config')
        def configFile = tempDir.resolve('config')

        cmd.getAuthFile() >> authFile
        cmd.getConfigFile() >> configFile

        // Create initial config file
        Files.writeString(configFile, "// Existing config\nparam1 = 'value1'\n")

        def config = [
            'tower.accessToken': 'test-token-123'
        ]

        when:
        cmd.writeConfig(config, null)

        then:
        Files.exists(configFile)
        def configContent = Files.readString(configFile)
        configContent.contains("includeConfig 'seqera-auth.config'")
        configContent.contains('param1 = \'value1\'')
    }

    def 'should not duplicate includeConfig line'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def authFile = tempDir.resolve('seqera-auth.config')
        def configFile = tempDir.resolve('config')

        cmd.getAuthFile() >> authFile
        cmd.getConfigFile() >> configFile

        // Create config file with existing includeConfig line
        Files.writeString(configFile, "// Config\nincludeConfig 'seqera-auth.config'\nparam1 = 'value1'\n")

        def config = [
            'tower.accessToken': 'test-token-123'
        ]

        when:
        cmd.writeConfig(config, null)

        then:
        Files.exists(configFile)
        def configContent = Files.readString(configFile)
        configContent.count("includeConfig 'seqera-auth.config'") == 1
    }

    def 'should get current workspace name when workspace exists'() {
        given:
        def cmd = new AuthCommandImpl()
        def workspaces = [
            [workspaceId: '123', orgName: 'TestOrg', workspaceName: 'TestWorkspace'],
            [workspaceId: '456', orgName: 'OtherOrg', workspaceName: 'OtherWorkspace']
        ]

        when:
        def result = cmd.getCurrentWorkspaceName(workspaces, '123')

        then:
        result == 'TestOrg / TestWorkspace'
    }

    def 'should get default workspace name when workspace does not exist'() {
        given:
        def cmd = new AuthCommandImpl()
        def workspaces = [
            [workspaceId: '123', orgName: 'TestOrg', workspaceName: 'TestWorkspace']
        ]

        when:
        def result = cmd.getCurrentWorkspaceName(workspaces, '999')

        then:
        result == 'None (Personal workspace)'
    }

    def 'should get config value from config'() {
        given:
        def cmd = new AuthCommandImpl()
        def config = ['tower.accessToken': 'token-from-config']
        SysEnv.push([:])  // Isolate from actual environment variables

        when:
        def result = cmd.getConfigValue(config, 'tower.accessToken', 'TOWER_ACCESS_TOKEN')

        then:
        result.value == 'token-from-config'
        result.source == 'nextflow config'
        result.fromConfig == true
        result.fromEnv == false

        cleanup:
        SysEnv.pop()
    }

    def 'should get config value from config for endpoint'() {
        given:
        def cmd = new AuthCommandImpl()
        def config = ['tower.endpoint': 'https://example.com']

        when:
        def result = cmd.getConfigValue(config, 'tower.endpoint', 'TOWER_API_ENDPOINT')

        then:
        result.value == 'https://example.com'
        result.source == 'nextflow config'
        result.fromConfig == true
        result.fromEnv == false
    }

    def 'should get config value from environment variable'() {
        given:
        def cmd = new AuthCommandImpl()
        def config = [:]

        and:
        SysEnv.push( ['TOWER_ACCESS_TOKEN': 'token-from-env'])

        when:
        def result = cmd.getConfigValue(config, 'tower.accessToken', 'TOWER_ACCESS_TOKEN')

        then:
        result.value == 'token-from-env'
        result.source == 'env var $TOWER_ACCESS_TOKEN'
        result.fromConfig == false
        result.fromEnv == true

        cleanup:
        SysEnv.pop()
    }

    def 'should return null when no config or env value is set'() {
        given:
        def cmd = new AuthCommandImpl()
        def config = [:]

        when:
        def result = cmd.getConfigValue(config, 'tower.endpoint', null)

        then:
        result.value == null
        result.source == null
        result.fromConfig == false
        result.fromEnv == false
    }

    def 'should prioritize config over env'() {
        given:
        def cmd = new AuthCommandImpl()
        def config = ['tower.accessToken': 'token-from-config']

        SysEnv.push(['TOWER_ACCESS_TOKEN': 'token-from-env'])

        when:
        def result = cmd.getConfigValue(config, 'tower.accessToken', 'TOWER_ACCESS_TOKEN')

        then:
        result.value == 'token-from-config'
        result.source == 'nextflow config'

        cleanup:
        SysEnv.pop()
    }

    def 'should prioritize config over env for endpoint'() {
        given:
        def cmd = new AuthCommandImpl()
        def config = ['tower.endpoint': 'https://config.example.com']

        SysEnv.push(['TOWER_API_ENDPOINT': 'https://env.example.com'])

        when:
        def result = cmd.getConfigValue(config, 'tower.endpoint', 'TOWER_API_ENDPOINT')

        then:
        result.value == 'https://config.example.com'
        result.source == 'nextflow config'

        cleanup:
        SysEnv.pop()
    }

    def 'should handle null environment variable name'() {
        given:
        def cmd = new AuthCommandImpl()
        def config = ['tower.enabled': true]

        when:
        def result = cmd.getConfigValue(config, 'tower.enabled', null)

        then:
        result.value == true
        result.fromConfig == true
        result.fromEnv == false
    }

    def 'should print status table with simple values'() {
        given:
        def cmd = new AuthCommandImpl()
        def rows = [
            ['Setting1', 'Value1', 'Source1'],
            ['Setting2', 'Value2', 'Source2'],
            ['Setting3', 'Value3', 'Source3']
        ]

        when:
        def lines = cmd.generateStatusTableLines(rows)
        def output = lines.join('\n')

        then:
        output.contains('Setting')
        output.contains('Value')
        output.contains('Source')
        output.contains('Setting1')
        output.contains('Value1')
        output.contains('Source1')
        output.contains('Setting2')
        output.contains('Value2')
        output.contains('Source2')
        output.contains('Setting3')
        output.contains('Value3')
        output.contains('Source3')
        output.contains('---') // Table separator
    }

    def 'should print status table with colored values'() {
        given:
        def cmd = new AuthCommandImpl()
        def rows = [
            ['API endpoint', ColorUtil.colorize('https://api.cloud.seqera.io', 'magenta'), 'config'],
            ['Authentication', ColorUtil.colorize('OK', 'green'), 'env var']
        ]

        when:
        def lines = cmd.generateStatusTableLines(rows)
        def output = lines.join('\n')

        then:
        output.contains('API endpoint')
        output.contains('https://api.cloud.seqera.io')
        output.contains('Authentication')
        output.contains('OK')
        output.contains('config')
        output.contains('env var')
    }

    def 'should handle empty status table'() {
        given:
        def cmd = new AuthCommandImpl()
        def rows = []

        when:
        def lines = cmd.generateStatusTableLines(rows)

        then:
        lines.isEmpty()
    }

    def 'should handle null status table'() {
        given:
        def cmd = new AuthCommandImpl()

        when:
        def lines = cmd.generateStatusTableLines(null)

        then:
        lines.isEmpty()
    }

    def 'should strip ANSI codes correctly'() {
        given:
        def cmd = new AuthCommandImpl()
        def textWithAnsi = '\u001B[31mRed Text\u001B[0m'
        def textWithMultipleAnsi = '\u001B[1;32mBold Green\u001B[0m \u001B[34mBlue\u001B[0m'

        when:
        def stripped1 = cmd.stripAnsiCodes(textWithAnsi)
        def stripped2 = cmd.stripAnsiCodes(textWithMultipleAnsi)

        then:
        stripped1 == 'Red Text'
        stripped2 == 'Bold Green Blue'
    }

    def 'should handle null text when stripping ANSI codes'() {
        given:
        def cmd = new AuthCommandImpl()

        when:
        def result = cmd.stripAnsiCodes(null)

        then:
        result == ''
    }

    def 'should handle empty text when stripping ANSI codes'() {
        given:
        def cmd = new AuthCommandImpl()

        when:
        def result = cmd.stripAnsiCodes('')

        then:
        result == ''
    }

    def 'should pad string with ANSI codes correctly'() {
        given:
        def cmd = new AuthCommandImpl()
        def plainText = 'Hello'
        def coloredText = '\u001B[32mHello\u001B[0m'

        when:
        def paddedPlain = cmd.padStringWithAnsi(plainText, 10)
        def paddedColored = cmd.padStringWithAnsi(coloredText, 10)

        then:
        // Plain text should be padded to 10 characters
        cmd.stripAnsiCodes(paddedPlain).length() == 10
        // Colored text should preserve ANSI codes but pad visible text to 10 characters
        cmd.stripAnsiCodes(paddedColored).length() == 10
        paddedColored.contains('\u001B[32m') // ANSI codes preserved
    }

    def 'should not pad if text is already at target width'() {
        given:
        def cmd = new AuthCommandImpl()
        def text = 'Hello'

        when:
        def result = cmd.padStringWithAnsi(text, 5)

        then:
        result == 'Hello'
    }

    def 'should not pad if text exceeds target width'() {
        given:
        def cmd = new AuthCommandImpl()
        def text = 'Hello World'

        when:
        def result = cmd.padStringWithAnsi(text, 5)

        then:
        result == 'Hello World'
    }

    def 'should print status table with varying column widths'() {
        given:
        def cmd = new AuthCommandImpl()
        def rows = [
            ['Short', 'V', 'S'],
            ['Very Long Setting Name', 'Very Long Value Here', 'Very Long Source']
        ]

        when:
        def lines = cmd.generateStatusTableLines(rows)
        def output = lines.join('\n')

        then:
        // Should handle different column widths properly
        output.contains('Short')
        output.contains('Very Long Setting Name')
        output.contains('Very Long Value Here')
        output.contains('Very Long Source')
        // All rows should be aligned
        lines.size() >= 4 // Header + separator + 2 data rows
    }

    def 'should apply minimum column widths'() {
        given:
        def cmd = new AuthCommandImpl()
        def rows = [
            ['A', 'B', 'C']
        ]

        when:
        def lines = cmd.generateStatusTableLines(rows)
        def output = lines.join('\n')

        then:
        // Even with short values, should apply minimum widths
        output.contains('Setting') // Header
        output.contains('Value')
        output.contains('Source')
        // Columns should be padded to at least minimum width
        lines.size() >= 3 // Header + separator + data row
    }

    def 'should collect status with valid authentication'() {
        given:
        def config = [
            'tower.accessToken': 'test-token',
            'tower.endpoint': 'https://api.cloud.seqera.io',
            'tower.enabled': true
        ]

        // Mock API calls
        def client = Mock(TowerClient){
            getUserInfo() >> [userName: 'testuser', id: '123']
            getWorkflowDetails(_, _) >> null
        }
        def cmd = Spy(new AuthCommandImpl())
        cmd.createTowerClient(_,_) >> client
        cmd.checkApiConnection(_) >> true
        cmd.listComputeEnvironments(_, _) >> [[name: 'ce_test', platform: 'aws', workDir: 's3://test', primary: true]]

        when:
        def status = cmd.collectStatus(config)

        then:
        status != null
        status.table.size() == 7 // endpoint, connection, auth, monitoring, workspace, compute env (no work dir when no primary env)
        // Check API endpoint row
        status.table[0][0] == 'API endpoint'
        status.table[0][1].contains('https://api.cloud.seqera.io')
        // Check API connection row
        status.table[1][0] == 'API connection'
        status.table[1][1].contains('OK')
        // Check authentication row
        status.table[2][0] == 'Authentication'
        status.table[2][1].contains('OK')
        status.table[2][1].contains('testuser')
        // Check monitoring row
        status.table[3][0] == 'Workflow monitoring'
        status.table[3][1].contains('Yes')
        // Check workspace row
        status.table[4][0] == 'Default workspace'
        status.table[4][1].contains('Personal workspace')
        // Check compute env row
        status.table[5][0] == 'Compute environment'
        status.table[5][1].contains('ce_test')
        //Workdir
        status.table[6][0] == 'Default work dir'
        status.table[6][1] == 's3://test'
    }

    def 'should collect status without authentication'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def config = [:]
        SysEnv.push([:])  // Isolate from actual environment variables

        cmd.checkApiConnection(_) >> true

        when:
        def status = cmd.collectStatus(config)

        then:
        status != null
        status.table.size() == 4 // endpoint, connection, auth, monitoring (no workspace/compute env/work dir without auth)
        // Authentication should show not set
        status.table[2][0] == 'Authentication'
        status.table[2][1].contains('Not set')
        status.table[2][2] == 'not set'

        cleanup:
        SysEnv.pop()
    }

    def 'should collect status with failed API connection'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def config = ['tower.endpoint': 'https://unreachable.example.com']

        cmd.checkApiConnection(_) >> false

        when:
        def status = cmd.collectStatus(config)

        then:
        status != null
        status.table[1][0] == 'API connection'
        status.table[1][1].contains('ERROR')
    }

    def 'should collect status with failed authentication'() {
        given:
        def config = ['tower.accessToken': 'invalid-token']
        SysEnv.push([:])  // Isolate from actual environment variables

        def client = Mock(TowerClient){
            getUserInfo() >> { throw new RuntimeException('Invalid token') }
        }
        def cmd = Spy(new AuthCommandImpl())
        cmd.createTowerClient(_,_) >> client
        cmd.checkApiConnection(_) >> true
        when:
        def status = cmd.collectStatus(config)

        then:
        status != null
        status.table[2][0] == 'Authentication'
        status.table[2][1].contains('Connection check failed')
        status.table[2][2] == 'nextflow config'

        cleanup:
        SysEnv.pop()
    }

    def 'should collect status with monitoring enabled'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def config = ['tower.enabled': true]

        cmd.checkApiConnection(_) >> true

        when:
        def status = cmd.collectStatus(config)

        then:
        status != null
        status.table[3][0] == 'Workflow monitoring'
        status.table[3][1].contains('Yes')
    }

    def 'should collect status with monitoring disabled'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def config = ['tower.enabled': false]

        cmd.checkApiConnection(_) >> true

        when:
        def status = cmd.collectStatus(config)

        then:
        status != null
        status.table[3][0] == 'Workflow monitoring'
        status.table[3][1].contains('No')
    }

    def 'should collect status with workspace details'() {
        given:

        def config = [
            'tower.accessToken': 'test-token',
            'tower.workspaceId': '12345'
        ]
        def client = Mock(TowerClient){
            getUserInfo() >> [userName: 'testuser', id: '123']
            getUserWorkspaceDetails(_, _) >> [
                orgName: 'TestOrg',
                workspaceName: 'TestWorkspace',
                workspaceFullName: 'test-org/test-workspace'
            ]
        }
        def cmd = Spy(new AuthCommandImpl())
        cmd.createTowerClient(_,_) >> client
        cmd.checkApiConnection(_) >> true

        when:
        def status = cmd.collectStatus(config)

        then:
        status != null
        status.table[4][0] == 'Default workspace'
        status.table[4][1].contains('12345')
        status.workspaceInfo != null
        status.workspaceInfo.orgName == 'TestOrg'
        status.workspaceInfo.workspaceName == 'TestWorkspace'
    }

    def 'should collect status with workspace ID but no details'() {
        given:

        def client = Mock(TowerClient){
            getUserInfo() >> [userName: 'testuser', id: '123']
            getUserWorkspaceDetails(_, _) >> null
        }
        def config = [
            'tower.accessToken': 'test-token',
            'tower.workspaceId': '12345'
        ]
        def cmd = Spy(new AuthCommandImpl())
        cmd.createTowerClient(_,_) >> client
        cmd.checkApiConnection(_) >> true

        when:
        def status = cmd.collectStatus(config)

        then:
        status != null
        status.table[4][0] == 'Default workspace'
        status.table[4][1].contains('12345')
        status.workspaceInfo == null
    }

    def 'should collect status from environment variables'() {
        given:
        def client  = Mock(TowerClient){
            getUserInfo() >> [userName: 'envuser', id: '456']
            getUserWorkspaceDetails(_, _) >> [:]
        }
        def config = [:]
        def cmd = Spy(new AuthCommandImpl())
        cmd.createTowerClient(_,_) >> client
        cmd.checkApiConnection(_) >> true
        cmd.listComputeEnvironments(_,_) >> []

        SysEnv.push(['TOWER_ACCESS_TOKEN': 'env-token',
                     'TOWER_API_ENDPOINT': 'https://env.example.com',
                     'TOWER_WORKFLOW_ID': 'workflow-123',
                     'TOWER_WORKSPACE_ID': 'ws-123'] )

        when:
        def status = cmd.collectStatus(config)


        then:
        status != null
        status.table[0][1].contains('https://env.example.com')
        status.table[0][2] == 'env var $TOWER_API_ENDPOINT'
        status.table[2][1].contains('envuser')
        status.table[2][2].contains('env var $TOWER_ACCESS_TOKEN')
        status.table[4][2].contains('env var $TOWER_WORKSPACE_ID')

        cleanup:
        SysEnv.pop()
    }

    def 'should collect status with default values'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def config = [:]

        cmd.checkApiConnection(_) >> true

        when:
        def status = cmd.collectStatus(config)

        then:
        status != null
        // Should use default endpoint
        status.table[0][1].contains('https://api.cloud.seqera.io')
        status.table[0][2] == 'default'
        // Should show monitoring disabled by default
        status.table[3][1].contains('No')
        status.table[3][2] == 'default'
    }

    def 'should collect status with mixed sources'() {
        given:
        def client = Mock(TowerClient) {
            getUserInfo() >> [userName: 'mixeduser', id: '789']
        }
        def cmd = Spy(new AuthCommandImpl())
        cmd.createTowerClient(_,_) >> client
        def authFile = tempDir.resolve('seqera-auth.config')
        def configFile = tempDir.resolve('config')
        cmd.getAuthFile() >> authFile
        cmd.getConfigFile() >> configFile

        def config = [
            'tower.enabled': true,
            'tower.accessToken': 'login-token'
        ]

        cmd.checkApiConnection(_) >> true
        SysEnv.push(['TOWER_WORKSPACE_ID': '99999'])

        when:
        def status = cmd.collectStatus(config)


        then:
        status != null
        // Token from nextflow config
        status.table[2][2] == 'nextflow config'
        // Enabled from nextflow config
        status.table[3][2] == 'nextflow config'
        // Workspace from env var
        status.table[4][2].contains('env var $TOWER_WORKSPACE_ID')

        cleanup:
        SysEnv.pop()
    }

    def 'should print status correctly'() {
        given:
        def cmd = new AuthCommandImpl()
        def status = new AuthCommandImpl.ConfigStatus(
            [
                ['API endpoint', 'https://api.cloud.seqera.io', 'default'],
                ['Authentication', 'OK', 'config']
            ],
            [
                orgName: 'TestOrg',
                workspaceName: 'TestWorkspace',
                workspaceFullName: 'test-org/test-workspace'
            ],
            1 // workspaceRowIndex
        )

        when:
        cmd.printStatus(status)
        def output = capture.toString()

        then:
        output.contains('API endpoint')
        output.contains('https://api.cloud.seqera.io')
        output.contains('Authentication')
        output.contains('OK')
        output.contains('TestOrg')
        output.contains('TestWorkspace')
    }

    def 'should print status without workspace info'() {
        given:
        def cmd = new AuthCommandImpl()
        def status = new AuthCommandImpl.ConfigStatus(
            [
                ['API endpoint', 'https://api.cloud.seqera.io', 'default']
            ],
            null,
            null
        )

        when:
        cmd.printStatus(status)
        def output = capture.toString()

        then:
        output.contains('API endpoint')
        !output.contains('TestOrg')
    }

    def 'should detect existing auth file and prevent duplicate login'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def authFile = tempDir.resolve('seqera-auth.config')
        Files.createFile(authFile)

        cmd.getAuthFile() >> authFile

        when:
        cmd.login('https://api.cloud.seqera.io')
        def output = capture.toString()

        then:
        output.contains('Error: Authentication token is already configured')
        output.contains('nextflow auth logout')
    }

    def 'should warn when TOWER_ACCESS_TOKEN env var is set during login'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def authFile = tempDir.resolve('seqera-auth.config-not-exists')

        cmd.getAuthFile() >> authFile
        cmd.performOidcLogin(_) >> { /* mock to prevent actual login */ }
        SysEnv.push(['TOWER_ACCESS_TOKEN': 'env-token'])

        when:
        cmd.login('https://api.cloud.seqera.io')
        def output = capture.toString()

        then:
        output.contains('WARNING: Authentication token is already configured via TOWER_ACCESS_TOKEN environment variable')

        cleanup:
        SysEnv.pop()
    }

    def 'should normalize API URL during login'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def authFile = tempDir.resolve('seqera-auth.config-not-exists')

        cmd.getAuthFile() >> authFile

        when:
        cmd.login('api.cloud.seqera.io')

        then:
        1 * cmd.performOidcLogin('https://api.cloud.seqera.io') >> { /* mock to prevent actual login */ }
    }

    def 'should use OIDC login for any endpoint'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def authFile = tempDir.resolve('seqera-auth.config-not-exists')

        cmd.getAuthFile() >> authFile

        when:
        cmd.login('https://enterprise.example.com')

        then:
        1 * cmd.performOidcLogin('https://enterprise.example.com') >> { /* mock */ }
    }

    def 'should save auth to config after successful PAT generation'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def authFile = tempDir.resolve('seqera-auth.config')
        def configFile = tempDir.resolve('config')

        cmd.getAuthFile() >> authFile
        cmd.getConfigFile() >> configFile

        def config = [
            'tower.accessToken': 'generated-pat-token',
            'tower.endpoint': 'https://api.cloud.seqera.io',
            'tower.enabled': true
        ]

        when:
        cmd.saveAuthToConfig('generated-pat-token', 'https://api.cloud.seqera.io')

        then:
        Files.exists(authFile)
        def content = Files.readString(authFile)
        content.contains('accessToken = \'generated-pat-token\'')
        content.contains('endpoint = \'https://api.cloud.seqera.io\'')
        content.contains('enabled = true')
    }

    def 'should generate PAT with correct token name'() {
        given:
        def cmd = Spy(AuthCommandImpl)
        def username = System.getProperty('user.name')
        def mockApiResponse = Mock(TowerClient.Response){
            getCode() >> 200
            getMessage() >> '{"accessKey":"generated-pat-123","id":"token-id-456"}'
        }
        def mockClient = Mock(TowerClient){
            sendApiRequest(_,_,_) >> mockApiResponse
        }

        cmd.createTowerClient(_,_) >> mockClient

        when:
        def pat = cmd.generatePAT('auth-token', 'https://api.cloud.seqera.io')

        then:
        pat == 'generated-pat-123'
    }

    def 'should fail PAT generation on error response'() {
        given:
        def cmd = Spy(AuthCommandImpl)

        def mockApiResponse = Mock(TowerClient.Response){
            getCode() >> 401
            getMessage() >> 'Unauthorized'
        }
        def mockClient = Mock(TowerClient){
            sendApiRequest(_,_,_) >> mockApiResponse
        }

        cmd.createTowerClient(_,_) >> mockClient

        when:
        cmd.generatePAT('invalid-token', 'https://api.cloud.seqera.io')

        then:
        def ex = thrown(RuntimeException)
        ex.message.contains('Failed to generate PAT')
    }

}
