/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * license agreements; and to You under the Apache License, version 2.0:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * This file is part of the Apache Pekko project, which was derived from Akka.
 */

package org.apache.pekko.grpc

import java.nio.file.{ Files, Path, Paths }

import scala.collection.JavaConverters._
import scala.sys.process._
import scala.util.matching.UnanchoredRegex

import org.eclipse.jgit.diff.RawText
import sbt._
import sbt.Keys._

object VersionSyncCheckPlugin extends AutoPlugin {
  override def trigger = allRequirements

  object autoImport {
    val grpcVersionSyncCheck = taskKey[Unit]("")
    val googleProtobufVersionSyncCheck = taskKey[Unit]("")
  }
  import autoImport._

  override def globalSettings = Seq(
    grpcVersionSyncCheck := versionSyncCheckImpl(
      "gRPC",
      Dependencies.Versions.grpc,
      raw"""(?i)grpc.?(?i)version.{1,9}(\d+\.\d+\.\d+)""".r.unanchored,
      Seq(
        Paths.get("plugin-tester-java/pom.xml"),
        Paths.get("plugin-tester-scala/pom.xml"),
        Paths.get("sbt-plugin/src/sbt-test/gen-scala-server/00-interop/build.sbt"),
        Paths.get("gradle-plugin/src/main/groovy/org/apache/pekko/grpc/gradle/PekkoGrpcPluginExtension.groovy"))).value,
    googleProtobufVersionSyncCheck := versionSyncCheckImpl(
      "Google Protobuf",
      Dependencies.Versions.googleProtoc,
      raw"""(?i)protoc_?version.*= ?"-?v?(\d+\.\d+\.\d+)"""".r.unanchored,
      Seq(
        Paths.get("maven-plugin/src/main/maven/plugin.xml"),
        Paths.get("gradle-plugin/src/main/groovy/org/apache/pekko/grpc/gradle/PekkoGrpcPluginExtension.groovy"))).value)

  def versionSyncCheckImpl(
      name: String,
      expectedVersion: String,
      VersionRegex: UnanchoredRegex,
      knownFiles: Seq[Path]) =
    Def.task[Unit] {
      val log = state.value.log
      log.info(s"Running $name version sync check, expecting version $expectedVersion")

      def versions(path: Path): (Path, Seq[String]) =
        (
          path,
          Files
            .lines(path)
            .iterator
            .asScala
            .collect {
              case VersionRegex(version) => version
            }
            .toSeq)

      log.info("Sanity checking regex extraction against known files")
      val mismatchVersions = knownFiles.map(versions).filterNot(_._2.toSet == Set(expectedVersion)).toVector
      if (mismatchVersions.isEmpty) {
        log.info("Sanity check passed")
      } else {
        mismatchVersions.foreach {
          case (path, versions) =>
            log.error(s"Found sanity check $name version mismatch: $path -> $versions")
        }
        fail("Sanity check failed")
      }

      val buildBase = (ThisBuild / baseDirectory).value
      val process = Process("git ls-tree -z --full-tree -r --name-only HEAD", buildBase)
      val paths = (process !! log).trim
        .split('\u0000')
        .iterator
        .map(path => Paths.get(path))
        .filter(Files.exists(_))
        .filterNot(path => RawText.isBinary(Files.newInputStream(path)))
        .filterNot(path => path.toString.endsWith(".enc")) // encrypted blob

      var mismatch = false

      for ((path, versions) <- paths.map(versions(_)).filter(_._2.nonEmpty)) {
        if (versions.forall(_ == expectedVersion)) {
          log.info(s"Found matching $name version $expectedVersion in $path")
        } else {
          log.error(s"Found $name version mismatch: $path -> $versions")
          mismatch = true
        }
      }

      if (mismatch) {
        fail(s"$name version sync check failed, expected $expectedVersion")
      }

      log.info(s"$name version sync check success")
    }

  private def fail(message: String): Nothing = {
    val fail = new MessageOnlyException(message)
    fail.setStackTrace(new Array[StackTraceElement](0))
    throw fail
  }
}
