import sbt._
import com.typesafe.config._

import scala.annotation.tailrec
import scala.jdk.CollectionConverters._
import java.util.{Map => JMap}

object EnvConfig {
  def serializeTo(cfg: Config, out: File): Unit =
    IO.write(out, serialize(cfg))

  def serialize(cfg: Config): String = {
    val buffer = new StringBuilder
    buffer.append("#### Server Configuration ####\n")
    for (
      entry <- cfg.entrySet().asScala.toList.sortBy(_.getKey)
      if isValidKey("docspell.server", entry)
    ) append(buffer, entry.getKey, entry.getValue)

    buffer.append("\n#### JOEX Configuration ####\n")
    for (
      entry <- cfg.entrySet().asScala.toList.sortBy(_.getKey)
      if isValidKey("docspell.joex", entry)
    ) append(buffer, entry.getKey, entry.getValue)

    buffer.toString().trim
  }

  private def append(buffer: StringBuilder, key: String, value: ConfigValue): Unit = {
    if (value.origin().comments().asScala.nonEmpty) {
      buffer.append("\n")
    }
    value
      .origin()
      .comments()
      .forEach(c => buffer.append("# ").append(c).append("\n"))
    buffer.append(keyToEnv(key)).append("=").append(value.render()).append("\n")
  }

  def isValidKey(prefix: String, entry: JMap.Entry[String, ConfigValue]): Boolean =
    entry.getKey
      .startsWith(prefix) && entry.getValue.valueType() != ConfigValueType.LIST

  def makeConfig(files: List[File]): Config =
    files
      .foldLeft(ConfigFactory.empty()) { (cfg, file) =>
        val cf = ConfigFactory.parseFile(file)
        cfg.withFallback(cf)
      }
      .withFallback(ConfigFactory.defaultOverrides(getClass.getClassLoader))
      .resolve()

  def makeConfig(file: File, files: File*): Config =
    makeConfig(file :: files.toList)

  def keyToEnv(k: String): String = {
    val buffer = new StringBuilder()
    val len = k.length

    @tailrec
    def go(current: Int): String =
      if (current >= len) buffer.toString()
      else {
        k.charAt(current) match {
          case '.' => buffer.append("_")
          case '-' => buffer.append("__")
          case '_' => buffer.append("___")
          case c   => buffer.append(c.toUpper)
        }
        go(current + 1)
      }

    go(0)
  }
}