/*
 * Copyright 2026 Signal Messenger, LLC
 * SPDX-License-Identifier: AGPL-3.0-only
 */

import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
import org.gradle.work.DisableCachingByDefault
import java.nio.charset.StandardCharsets
import javax.inject.Inject

/**
 * Rebuilds dependency verification components from the dependencies resolved by the build.
 *
 * Gradle only adds entries when --write-verification-metadata is used, so starting with an empty
 * components section is what removes entries for dependencies that are no longer used. The
 * configuration section is retained, including its trusted-artifact rules.
 */
@DisableCachingByDefault(because = "This task updates Gradle's dependency verification metadata")
abstract class UpdateVerificationMetadataTask
@Inject
constructor(
  private val execOperations: ExecOperations
) : DefaultTask() {
  @get:Internal
  abstract val metadataFile: RegularFileProperty

  @get:Internal
  abstract val rootDirectory: DirectoryProperty

  @get:Internal
  abstract val wrapperCommand: ListProperty<String>

  @TaskAction
  fun update() {
    val file = metadataFile.get().asFile
    val original = file.readBytes()

    try {
      val metadata = original.toString(StandardCharsets.UTF_8)
      val components = Regex("(?s)(<components>).*?(</components>)")
      check(components.findAll(metadata).count() == 1) {
        "Expected exactly one <components> section in ${file.absolutePath}"
      }

      file.writeText(
        components.replace(metadata) { match ->
          "${match.groupValues[1]}\n   ${match.groupValues[2]}"
        },
        StandardCharsets.UTF_8
      )

      // The qa build resolves everything a clean qa run needs, including tooling that is only pulled while a
      // task executes -- the KSP compiler, the aapt2 executable, and the layoutlib screenshot renderer all come
      // in through detached configurations. Finally the cross-platform sync fills in aapt2's other OS variants.
      runGradle("--write-verification-metadata", "sha256", "qa")
      // The build-logic precompiled-script-plugin accessors are generated by applying each plugin in throwaway
      // nested builds, which resolve plugin dependencies (e.g. junit BOMs) that no configuration walk or qa build
      // ever sees. Forcing a refresh re-downloads them through the verifier so their checksums get written.
      runGradle("--write-verification-metadata", "sha256", ":build-logic:plugins:generatePrecompiledScriptPluginAccessors", "--rerun-tasks", "--no-build-cache", "--refresh-dependencies")
      // The IDE resolves the bundled Groovy module graph (via localGroovy) during sync's model-building phase, which
      // no task graph reaches, so resolve it explicitly to capture those checksums.
      runGradle("--write-verification-metadata", "sha256", ":build-logic:plugins:syncGroovyVerification", "--rerun-tasks", "--refresh-dependencies")
      runGradle("--write-verification-metadata", "sha256", "syncCrossPlatformVerification", "--rerun-tasks")
    } catch (failure: Throwable) {
      file.writeBytes(original)
      throw failure
    }
  }

  private fun runGradle(vararg arguments: String) {
    execOperations.exec {
      workingDir(rootDirectory.get().asFile)
      commandLine(wrapperCommand.get() + arguments)
    }
  }
}
