// 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
//
//   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.

import sbt._
import sbt.Keys._
import com.typesafe.sbt.packager.universal.UniversalPlugin.autoImport._

/**
 * Generates per-module LICENSE files for jar META-INF and dist zip top level.
 *
 * Each jar's META-INF/LICENSE describes only what is in that specific jar:
 *  - Modules without vendored code get Apache 2.0 only.
 *  - workflow-operator gets Apache 2.0 plus the mbknor-jackson-jsonschema
 *    attribution and the full MIT license text.
 *
 * NOTICE and DISCLAIMER are copied as-is from the repo root.
 *
 * See https://github.com/apache/texera/issues/4131
 */
object AddMetaInfLicenseFiles {

  private lazy val rootDir = LocalRootProject / baseDirectory

  private val ThirdPartyHeader = "THIRD-PARTY DEPENDENCIES"

  /** Extract the Apache 2.0 license text (before the THIRD-PARTY section) from root LICENSE. */
  private def apacheLicenseText(rootDir: File): String = {
    val lines = IO.readLines(rootDir / "LICENSE")
    val headerIndex = lines.indexWhere(_.trim == ThirdPartyHeader)
    val cutoffIndex =
      if (headerIndex >= 0) {
        // Cut at the "---" delimiter line preceding the header
        val delimiterIndex = lines.lastIndexWhere(_.startsWith("---"), headerIndex - 1)
        if (delimiterIndex >= 0) delimiterIndex else headerIndex
      } else {
        lines.length
      }
    lines.take(cutoffIndex).mkString("\n").trim + "\n"
  }

  /** The vendored code section for workflow-operator (mbknor-jackson-jsonschema). */
  private def workflowOperatorVendoredSection(rootDir: File): String = {
    val mitLicense = IO.read(rootDir / "licenses" / "LICENSE-MIT.txt")
    s"""
       |--------------------------------------------------------------------------------
       |THIRD-PARTY DEPENDENCIES
       |--------------------------------------------------------------------------------
       |
       |This jar bundles compiled code from the following third-party project.
       |The full license text is included below.
       |
       |MIT License
       |--------------------------------------
       |
       |This product bundles code derived from mbknor-jackson-jsonschema:
       |  - com/kjetland/jackson/jsonSchema/
       |  Copyright (c) 2016 Kjell Tore Eliassen (mbknor)
       |  Source: https://github.com/mbknor/mbknor-jackson-jsonschema
       |
       |--------------------------------------------------------------------------------
       |Full text of the MIT License:
       |--------------------------------------------------------------------------------
       |
       |${mitLicense.trim}
       |""".stripMargin
  }

  private def writeToMetaInf(managed: File, fileName: String, content: String): File = {
    val dest = managed / "META-INF" / fileName
    IO.write(dest, content)
    dest
  }

  private def copyToMetaInf(managed: File, src: File, fileName: String): File = {
    val dest = managed / "META-INF" / fileName
    IO.copyFile(src, dest)
    dest
  }

  private def noticeAndDisclaimer(managed: File, rootDir: File): Seq[File] = {
    val files = Seq(copyToMetaInf(managed, rootDir / "NOTICE", "NOTICE"))
    val disclaimer = rootDir / "DISCLAIMER"
    if (disclaimer.exists()) files :+ copyToMetaInf(managed, disclaimer, "DISCLAIMER")
    else files
  }

  /** Settings for modules WITHOUT vendored third-party code.
   *  META-INF/LICENSE contains only the Apache 2.0 license text. */
  lazy val defaultSettings: Seq[Setting[_]] = Seq(
    Compile / resourceGenerators += Def.task {
      val managed = (Compile / resourceManaged).value
      val root = rootDir.value
      val licenseContent = apacheLicenseText(root)
      writeToMetaInf(managed, "LICENSE", licenseContent) +: noticeAndDisclaimer(managed, root)
    }.taskValue
  )

  /** Settings for workflow-operator which contains vendored mbknor-jackson-jsonschema code.
   *  META-INF/LICENSE contains Apache 2.0 plus the mbknor attribution and MIT license text. */
  lazy val workflowOperatorSettings: Seq[Setting[_]] = Seq(
    Compile / resourceGenerators += Def.task {
      val managed = (Compile / resourceManaged).value
      val root = rootDir.value
      val licenseContent = apacheLicenseText(root) + "\n" + workflowOperatorVendoredSection(root)
      writeToMetaInf(managed, "LICENSE", licenseContent) +: noticeAndDisclaimer(managed, root)
    }.taskValue
  )

  /** Ships the module's per-module LICENSE-binary, NOTICE-binary, DISCLAIMER
   *  (if present), and licenses/ at the Universal zip's top level. The
   *  per-module files describe only the third-party content actually
   *  bundled in this module's dist zip; the licenses/ directory at the
   *  repo root is shared by all dist zips. DISCLAIMER is optional (it
   *  will be removed at graduation). */
  def distMappings(
    existing: Seq[(File, String)],
    rootDir: File,
    licenseBinary: File,
    noticeBinary: File
  ): Seq[(File, String)] = {
    val disclaimerFile = rootDir / "DISCLAIMER"
    val licensesDir = rootDir / "licenses"

    require(licenseBinary.isFile,
      s"LICENSE-binary not found at $licenseBinary; required for binary-distribution packaging.")
    require(noticeBinary.isFile,
      s"NOTICE-binary not found at $noticeBinary; required for binary-distribution packaging.")
    require(licensesDir.isDirectory,
      s"licenses/ directory not found at $licensesDir; required for binary-distribution packaging.")

    val reserved = Set("LICENSE", "NOTICE", "DISCLAIMER")
    val filtered = existing.filterNot {
      case (_, path) => reserved.contains(path) || path.startsWith("licenses/")
    }

    val licenseTexts = (licensesDir ** "*.txt").get.map(f => f -> s"licenses/${f.getName}")

    val base = Seq(licenseBinary -> "LICENSE", noticeBinary -> "NOTICE")
    val disclaimer =
      if (disclaimerFile.isFile) Seq(disclaimerFile -> "DISCLAIMER")
      else Seq.empty

    filtered ++ base ++ disclaimer ++ licenseTexts
  }
}
