/*
 * This file is part of LibEuFin.
 * Copyright (C) 2023-2025 Taler Systems S.A.

 * LibEuFin is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3, or
 * (at your option) any later version.

 * LibEuFin is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
 * Public License for more details.

 * You should have received a copy of the GNU Affero General Public
 * License along with LibEuFin; see the file COPYING.  If not, see
 * <http://www.gnu.org/licenses/>
 */

package tech.libeufin.common

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.core.ProgramResult
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.groups.OptionGroup
import com.github.ajalt.clikt.parameters.groups.provideDelegate
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.enum
import com.github.ajalt.clikt.parameters.types.path
import kotlinx.coroutines.runBlocking
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.event.Level

private val logger: Logger = LoggerFactory.getLogger("libeufin-config")

abstract class TalerCmd(name: String? = null): CliktCommand(name) {
    val config by option(
        "--config", "-c",
        help = "Specifies the configuration file",
        metavar = "config_file"
    ).path()
    val log by option(
        "--log", "-L",
        help = "Configure logging to use LOGLEVEL"
    ).enum<Level>().default(Level.INFO)

    fun cliCmd(logger: Logger, lambda: suspend () -> Unit) {
        // Set log level
        TalerServiceProvider.currentLevel = log
        // Run cli command catching all errors
        try {
            runBlocking {
                lambda()
            }
        } catch (e: ProgramResult) {
            throw e
        } catch (e: Throwable) {
            e.fmtLog(logger)
            throw ProgramResult(1)
        }
    }
}

class CliConfigCmd(configSource: ConfigSource) : TalerCmd("config") {
    init {
        subcommands(CliConfigGet(configSource), CliConfigDump(configSource), CliConfigPathsub(configSource))
    }

    override fun help(context: Context) = "Inspect or change the configuration"

    override fun run() = Unit
}

private class CliConfigGet(private val configSource: ConfigSource) : TalerCmd("get") {
    override fun help(context: Context) = "Lookup config value"

    private val isPath by option(
        "--filename", "-f",
        help = "Interpret value as path with dollar-expansion"
    ).flag()
    private val section by argument()
    private val option by argument()

    override fun run() = cliCmd(logger) {
        val config = configSource.fromFile(config)
        val sect = config.section(section)
        if (isPath) {
            println(sect.path(option).require())
        } else {
            println(sect.string(option).require())
        }
    }
}



private class CliConfigPathsub(private val configSource: ConfigSource) : TalerCmd("pathsub") {
    override fun help(context: Context) = "Substitute variables in a path"

    private val pathExpr by argument()

    override fun run() = cliCmd(logger) {
        val config = configSource.fromFile(config)
        println(config.pathsub(pathExpr))
    }
}

private class CliConfigDump(private val configSource: ConfigSource) : TalerCmd("dump") {
    override fun help(context: Context) = "Dump the configuration"

    override fun run() = cliCmd(logger) {
        val config = configSource.fromFile(config)
        println("# install path: ${configSource.installPath()}")
        println(config.stringify())
    }
}
