/*
 * This file is part of LibEuFin.
 * Copyright (C) 2025, 2026 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.nexus.cli

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.parameters.arguments.*
import com.github.ajalt.clikt.parameters.groups.provideDelegate
import com.github.ajalt.clikt.parameters.options.convert
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.*
import com.github.ajalt.mordant.terminal.*
import tech.libeufin.common.*
import tech.libeufin.nexus.*
import tech.libeufin.nexus.iso20022.*
import java.util.zip.*
import java.time.Instant
import java.io.*

private fun fmtPayto(payto: String): String {
    try {
        val parsed = Payto.parse(payto).expectIban()
        return buildString {
            append(parsed.iban.toString())
            if (parsed.bic != null) append(" ${parsed.bic}")
            if (parsed.receiverName != null) append(" ${parsed.receiverName}")
        }
    } catch (e: Exception) {
        return payto.removePrefix("payto://")
    }
}


class ListIncoming: TalerCmd("incoming") {
    override fun help(context: Context) = "List incoming transactions"

    private val incomplete by option(
        "--incomplete",
        help = "Only list transactions that are incomplete",
    ).flag()


    override fun run() = cliCmd(logger) {
        nexusConfig(config).withDb { db, cfg ->
            val txs = db.list.incoming(incomplete)
            for (tx in txs) {
                println(buildString{
                    if (tx.creditFee.isZero()) {
                        append("${tx.date} ${tx.id} ${tx.amount}\n")
                    } else {
                        append("${tx.date} ${tx.id} ${tx.amount}-${tx.creditFee}\n")
                    }
                    if (tx.debtor != null) {
                        append("  debtor: ${fmtPayto(tx.debtor)}\n")
                    }
                    if (tx.subject != null) {
                        append("  subject: ${tx.subject}\n")
                    }
                    if (tx.talerable != null) {
                        append("  talerable: ${tx.talerable}\n")
                    }
                    if (tx.bounced != null) {
                        append("  bounced: ${tx.bounced}\n")
                    }
                })
            }
        }
    }
}

class ListOutgoing: TalerCmd("outgoing") {
    override fun help(context: Context) = "List outgoing transactions"

    override fun run() = cliCmd(logger) {
        nexusConfig(config).withDb { db, cfg ->
            val txs = db.list.outgoing()
            for (tx in txs) {
                println(buildString{
                    append("${tx.date} ${tx.id} ${tx.amount}\n")
                    if (tx.creditor != null) {
                        append("  creditor: ${fmtPayto(tx.creditor)}\n")
                    }
                    append("  subject: ${tx.subject}\n")
                    if (tx.wtid != null) {
                        append("  talerable: ${tx.wtid} ${tx.exchangeBaseUrl}\n")
                    }
                })
            }
        }
    }
}

class ListInitiated: TalerCmd("initiated") {
    override fun help(context: Context) = "List initiated transactions"

    private val awaitingAck by option(
        "--ack", "--awaiting-ack",
        help = "Only list transactions awaiting manual acknowledgement",
    ).flag()

    override fun run() = cliCmd(logger) {
        nexusConfig(config).withDb { db, cfg ->
            if (awaitingAck) {
                val txs = db.list.initiatedAck()
                for (tx in txs) {
                    println(buildString{
                        append("${tx.date} ${tx.id} ${tx.amount}\n")
                        append("  creditor: ${fmtPayto(tx.creditor)}\n")
                        append("  subject: ${tx.subject}\n")
                        append("  ack: ${tx.dbId}")
                        append('\n')
                    })
                }
            } else {
                val txs = db.list.initiated()
                for (tx in txs) {
                    println(buildString{
                        append("${tx.date} ${tx.id} ${tx.amount}\n")
                        append("  creditor: ${fmtPayto(tx.creditor)}\n")
                        append("  subject: ${tx.subject}\n")
                        if (tx.batch != null) {
                            append("  batch: ${tx.batch}")
                            if (tx.batchOrder != null)
                            append(" ${tx.batchOrder}")
                            append('\n')
                        }
                        if (tx.submissionCounter > 0) {
                            append("  submission: ${tx.submissionTime} ${tx.submissionCounter}\n")
                        }
                        append("  status: ${tx.status}")
                        if (tx.msg != null) {
                            append(" ${tx.msg}")
                        }
                        append('\n')
                    })
                }
            }  
        }
    }
}


class ListCmd: CliktCommand("list") {
    override fun help(context: Context) = "List nexus transactions"

    init {
        subcommands(ListIncoming(), ListOutgoing(), ListInitiated())
    }

    override fun run() = Unit
}