/*
 * 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 nextflow.module

import org.yaml.snakeyaml.Yaml
import spock.lang.Specification
import spock.lang.TempDir

import java.nio.file.Path

/**
 * Tests for ModuleSpec
 *
 * @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
 */
class ModuleSpecTest extends Specification {

    @TempDir
    Path tempDir

    // =========================================================================
    // Render tests
    // =========================================================================

    def 'should validate complete spec' () {
        given:
        def spec = new ModuleSpec(
            name: 'nf-core/fastqc',
            version: '1.0.0',
            description: 'FastQC quality control',
            license: 'MIT'
        )

        when:
        def errors = spec.validate()

        then:
        errors.isEmpty()
        spec.isValid()
    }

    def 'should detect missing required fields' () {
        given:
        def spec = new ModuleSpec(
            name: 'nf-core/fastqc'
            // missing version, description, license
        )

        when:
        def errors = spec.validate()

        then:
        errors.size() == 3
        errors.any { it.contains('version') }
        errors.any { it.contains('description') }
        errors.any { it.contains('license') }
        !spec.isValid()
    }

    def 'should validate version format' () {
        given:
        def spec = new ModuleSpec(
            name: 'nf-core/fastqc',
            version: version,
            description: 'Test',
            license: 'MIT'
        )

        when:
        def errors = spec.validate()

        then:
        errors.isEmpty() == valid

        where:
        version         | valid
        '1.0.0'         | true
        '1.0.0-alpha'   | true
        '1.0.0-beta.1'  | true
        '1.0'           | false
        'v1.0.0'        | false
        '1.0.0.0'       | false
    }

    def 'should validate module name format' () {
        given:
        def spec = new ModuleSpec(
            name: name,
            version: '1.0.0',
            description: 'Test',
            license: 'MIT'
        )

        when:
        def errors = spec.validate()

        then:
        errors.isEmpty() == valid

        where:
        name                        | valid
        'nf-core/fastqc'            | true
        'myorg/my-module'           | true
        'org_1/tool_2'              | true
        'nf-core/gfatools/gfa2fa'   | true   // nested module path
        'myorg/tools/sub/module'    | true   // deeply nested
        'org.name/tool/sub'         | true   // dot in namespace
        'fastqc'                    | false
        '@nf-core/fastqc'           | false
        'nf-core/fast qc'           | false
        'nf-core/'                  | false  // trailing slash
        '/nf-core/fastqc'           | false  // leading slash
    }

    // =========================================================================
    // Render tests
    // =========================================================================

    def 'should render YAML with comment header'() {
        given:
        def spec = new ModuleSpec(name: 'my-namespace/fastqc')

        when:
        def yaml = spec.toYaml()

        then:
        yaml.startsWith('# This file was auto-generated by `nextflow module spec`.')
        yaml.contains('# Review and complete all fields marked "TODO" before publishing.')
    }

    def 'should render TODO list for missing required fields'() {
        given:
        def spec = new ModuleSpec(name: 'my-namespace/fastqc')
        // version, description, license are all missing

        when:
        def yaml = spec.toYaml()

        then:
        yaml.contains('# TODO:')
        yaml.contains('Missing required field: version')
        yaml.contains('Missing required field: description')
        yaml.contains('Missing required field: license')
    }

    def 'should not render TODO list when all required fields are present'() {
        given:
        def spec = new ModuleSpec(
            name: 'nf-core/fastqc',
            version: '1.0.0',
            description: 'Run FastQC',
            license: 'MIT'
        )

        when:
        def yaml = spec.toYaml()

        then:
        !yaml.contains('# TODO:')
    }

    def 'should omit null fields and include present fields in rendered YAML'() {
        given:
        def spec = new ModuleSpec(
            name: 'nf-core/fastqc',
            version: '1.0.0',
            description: 'Run FastQC',
            license: 'MIT'
        )

        when:
        def parsed = new Yaml().load(spec.toYaml()) as Map

        then:
        parsed['name'] == 'nf-core/fastqc'
        parsed['version'] == '1.0.0'
        parsed['description'] == 'Run FastQC'
        parsed['license'] == 'MIT'
    }

    def 'should omit input and output sections when empty'() {
        given:
        def spec = new ModuleSpec(name: 'my-namespace/simple')

        when:
        def yaml = spec.toYaml()

        then:
        !yaml.contains('input:')
        !yaml.contains('output:')
        !yaml.contains('topics:')
    }

    def 'should include passthrough fields when present'() {
        given:
        def spec = new ModuleSpec(
            name: 'nf-core/fastqc',
            version: '1.0.0',
            _passthrough: [foo: 'bar']
        )

        when:
        def parsed = new Yaml().load(spec.toYaml()) as Map

        then:
        parsed['name'] == 'nf-core/fastqc'
        parsed['version'] == '1.0.0'
        parsed['foo'] == 'bar'
    }

}
