/*
 * This file is part of LibEuFin.
 * Copyright (C) 2024-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.bank.cli

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.convert
import com.github.ajalt.clikt.parameters.arguments.optional
import com.github.ajalt.clikt.parameters.groups.OptionGroup
import com.github.ajalt.clikt.parameters.groups.cooccurring
import com.github.ajalt.clikt.parameters.groups.provideDelegate
import com.github.ajalt.clikt.parameters.options.*
import kotlinx.serialization.json.Json
import tech.libeufin.bank.*
import tech.libeufin.bank.api.*
import tech.libeufin.bank.db.AccountDAO.*
import tech.libeufin.common.*

class CreateAccountOption: OptionGroup() {
    val username: String by option(
        "-u", "--user", "--username",
        metavar = "<username>",
        help = "Account unique username"
    ).required()
    val password: String by option(
        "--password", "-p",
        help = "Account password used for authentication"
    ).prompt(requireConfirmation = true, hideInput = true)
    val name: String by option(
        help = "Legal name of the account owner"
    ).required()
    val is_public: Boolean by option(
        "--public",
        help = "Make this account visible to anyone"
    ).flag()
    val exchange: Boolean by option(
        help = "Make this account a taler exchange"
    ).flag()
    val email: String? by option(help = "E-Mail address used for TAN transmission")
    val phone: String? by option(help = "Phone number used for TAN transmission")
    val cashout_payto_uri: IbanPayto? by option(
        help = "Payto URI of a fiant account who receive cashout amount"
    ).convert { Payto.parse(it).expectIban() }
    val payto_uri: Payto? by option(
        help = "Payto URI of this account"
    ).convert { Payto.parse(it) }
    val debit_threshold: TalerAmount? by option(
        help = "Max debit allowed for this account"
    ).convert { TalerAmount(it) }
    val tan_channel: TanChannel? by option(
        help = "Enables 2FA and set the TAN channel used for challenges"
    ).convert { TanChannel.valueOf(it) }
}

class CreateAccount : TalerCmd("create-account") {
    override fun help(context: Context) = "Create an account, returning the payto://-URI associated with it"

    private val json by argument().convert { Json.decodeFromString<RegisterAccountRequest>(it) }.optional()
    private val options by CreateAccountOption().cooccurring()
 
    override fun run() = cliCmd(logger) {
        bankConfig(config).withDb { db, cfg ->
            val req = json ?: options?.run {
                RegisterAccountRequest(
                    username = username,
                    password = password,
                    name = name,
                    is_public = is_public,
                    is_taler_exchange = exchange,
                    contact_data = ChallengeContactData(
                        email = Option.Some(email),
                        phone = Option.Some(phone), 
                    ),
                    cashout_payto_uri = cashout_payto_uri,
                    payto_uri = payto_uri,
                    debit_threshold = debit_threshold,
                    tan_channel = tan_channel
                ) 
            }
            req?.let {
                when (val result = createAccount(db, cfg, req, true)) {
                    AccountCreationResult.BonusBalanceInsufficient ->
                        throw Exception("Insufficient admin funds to grant bonus")
                    AccountCreationResult.UsernameReuse ->
                        throw Exception("Account username reuse '${req.username}'")
                    AccountCreationResult.PayToReuse ->
                        throw Exception("Bank internalPayToUri reuse")
                    AccountCreationResult.UnknownConversionClass ->
                        throw Exception("Unknown conversion class ${req.conversion_rate_class_id}")
                    is AccountCreationResult.Success -> {
                        logger.info("Account '${req.username}' created")
                        println(result.payto)
                    }
                }
            }
        }
    }
}