/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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
 *
 *    https://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.
 */

import org.apache.grails.gradle.tasks.bom.ExtractDependenciesTask
import org.apache.grails.gradle.tasks.bom.ExtractedDependencyConstraint
import org.apache.grails.gradle.tasks.bom.PropertyNameCalculator

buildscript {
    apply from: rootProject.layout.projectDirectory.file('dependencies.gradle')
}

plugins {
    id 'java-platform'
    id 'maven-publish'
    id 'org.apache.grails.buildsrc.publish'
    id 'org.apache.grails.buildsrc.sbom'
}

version = projectVersion
group = 'org.apache.grails'

javaPlatform {
    allowDependencies()
}

ext {
    isReleaseBuild = System.getenv('GRAILS_PUBLISH_RELEASE') == 'true'
    isPublishedExternal = System.getenv().containsKey('NEXUS_PUBLISH_STAGING_PROFILE_ID')
}

dependencies {
    api platform(gradleBomPlatformDependencies['spring-boot-bom']), {
        exclude group: 'org.apache.groovy'
        exclude group: 'org.spockframework'
        exclude group: 'com.fasterxml.jackson'
    }

    for (def entry : bomPlatformDependencies.entrySet()) {
        api platform(entry.value)
    }

    constraints {
        for (def entry : gradleBomDependencies.entrySet()) {
            api entry.value
        }
        for (def entry : bomDependencies.entrySet()) {
            api entry.value
        }

        for (Project subproject : rootProject.subprojects) {
            if (subproject.path == project.path || subproject.name in testProjects || subproject.name in docProjects || subproject.name in cliProjects) {
                continue // ignore self & test projects
            }

            api project(":${subproject.path}")
        }

        // since we're duplicating the bom instead of inheriting we have to add the grails-gradle projects
        // TODO: It should be possible to pull these build names using includedBuild, but I haven't found a way to do so
        api "org.apache.grails:grails-gradle-plugins:${projectVersion}"
        api "org.apache.grails.gradle:grails-gradle-model:${projectVersion}"
        api "org.apache.grails:grails-docs-core:${projectVersion}"
        api "org.apache.grails.gradle:grails-gradle-common:${projectVersion}"
        api "org.apache.grails:grails-gradle-tasks:${projectVersion}"

        // because we are not yet publishing hibernate6, just force the hibernate5 version for now
        api("org.liquibase:liquibase-core") {
            version {
                strictly "$liquibaseHibernate5Version"
            }
        }
        api("org.liquibase.ext:liquibase-hibernate5") {
            version {
                strictly "$liquibaseHibernate5Version"
            }
        }
    }
}

configurations.register('bomDependencies').configure {
    it.canBeResolved = true
    it.transitive = true
    it.extendsFrom(configurations.named('api').get())
}

tasks.register('extractConstraints', ExtractDependenciesTask).configure { ExtractDependenciesTask it ->
    it.configuration = configurations.named('bomDependencies')
    it.configurationName = 'bomDependencies'
    it.destination = project.layout.buildDirectory.file('grails-bom-constraints.adoc')
    it.platformDefinitions = combinedPlatforms
    it.definitions = combinedDependencies
    it.projectName = project.name
    it.versions = combinedVersions
    rootProject.subprojects.each { p ->
        evaluationDependsOn(p.path)
    }
    it.projectArtifactIds.set(project.provider{
        Map<String, String> artifactIdMappings = [:]

        rootProject.subprojects.each { p ->
            artifactIdMappings[p.name] = p.findProperty('pomArtifactId') ?: p.name
        }

        artifactIdMappings
    })

    it.dependsOn(project.tasks.named('generateMetadataFileForMavenPublication'), project.tasks.named('generatePomFileForMavenPublication'))
}

def validateNoSnapshotDependencies = tasks.register('validateNoSnapshotDependencies')
validateNoSnapshotDependencies.configure { Task it ->
    it.group = 'publishing'
    it.description = 'Validates that no snapshot dependencies are present in the project when performing a release.'

    it.doLast {
        configurations.each { config ->
            config.allDependencies.each { dep ->
                if (dep.version && dep.version.contains('-SNAPSHOT')) {
                    throw new GradleException("Releases cannot have a snapshot dependency: ${dep.group}:${dep.name} (${dep.version})")
                }
            }
        }
    }
}

if (ext.isReleaseBuild && ext.isPublishedExternal) {
    project.afterEvaluate {
        tasks.named('generateMetadataFileForMavenPublication').configure {
            dependsOn(validateNoSnapshotDependencies)
        }
        tasks.named('generatePomFileForMavenPublication').configure {
            dependsOn(validateNoSnapshotDependencies)
        }
    }
}

ext {
    pomDescription = 'Grails BOM (Bill of Materials) for managing dependency versions used by Grails Projects'
    pomCustomization = { xml ->
        def root = xml.asNode()

        def propertiesNode = root.properties ? root.properties[0] : root.appendNode('properties')

        def depMgmt = root.dependencyManagement?.getAt(0)
        def deps = depMgmt?.dependencies?.getAt(0)
        if (deps) {
            Map<String, String> pomProperties = [:]
            deps.dependency.each { dep ->
                String groupId = dep.groupId.text().trim()
                String artifactId = dep.artifactId.text().trim()
                boolean isBom = dep.scope.text().trim() == 'import'

                String inlineVersion = dep.version.text().trim()
                if (inlineVersion == 'null') {
                    inlineVersion = null
                }

                if (inlineVersion) {
                    PropertyNameCalculator propertyNameCalculator = new PropertyNameCalculator(combinedPlatforms, combinedDependencies, combinedVersions)
                    propertyNameCalculator.addProjects(rootProject.subprojects)
                    ExtractedDependencyConstraint extractedConstraint = propertyNameCalculator.calculate(groupId, artifactId, inlineVersion, isBom)
                    if (extractedConstraint?.versionPropertyReference) {
                        // use the property reference instead of the hard coded version so that it can be
                        // overriden by the spring boot dependency management plugin
                        dep.version[0].value = extractedConstraint.versionPropertyReference

                        // Add an entry in the <properties> node with the actual version number
                        pomProperties.put(extractedConstraint.versionPropertyName, inlineVersion)
                    }

                    if (gradle.includedBuilds*.any { it.name == artifactId && groupId.startsWith('org.apache.grails') }) {
                        String baseVersionName = artifactId.replaceAll('-', '.')
                        String versionName = "${baseVersionName}.version" as String
                        dep.version[0].value = "\${${versionName}}" as String
                        pomProperties.put(versionName, inlineVersion)
                    }
                } else if (!inlineVersion) {
                    throw new GradleException("Dependency $groupId:$artifactId does not have a version.")
                }
            }

            for (Map.Entry<String, String> property : pomProperties.entrySet()) {
                propertiesNode.appendNode(property.key, property.value)
            }
        }
    }
}
