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

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import org.slf4j.LoggerFactory
import tech.libeufin.common.TalerAmount
import tech.libeufin.common.TransferStatusState
import tech.libeufin.common.IbanPayto
import tech.libeufin.common.db.DatabaseConfig
import tech.libeufin.common.db.DbPool
import tech.libeufin.common.db.watchNotifications
import tech.libeufin.ebics.EbicsDAO
import java.time.Instant

/** Batch of initiated outgoing payment to sent together */
data class PaymentBatch(
    val id: Long,
    val messageId: String,
    val creationDate: Instant,
    val sum: TalerAmount,
    val payments: List<InitiatedPayment>,
)

/** Initiated outgoing transaction */
data class InitiatedPayment(
    val id: Long,
    val amount: TalerAmount,
    val subject: String,
    val creditor: IbanPayto,
    val initiationTime: Instant,
    val endToEndId: String
)

enum class StatusUpdate {
    pending,
    transient_failure,
    permanent_failure,
    success
}

/** Outgoing transactions and batches submission status */
enum class SubmissionState {
    // Initiated but not yet submitted
    unsubmitted,
    // Submission failed, retry possible
    transient_failure,
    // Submission succeed, pending settltment
    pending,
    // Definitive failure, will never succeed
    permanent_failure,
    // Definitive success, booked and settled
    success,
    // Late failure after a success, happens when a payment is returned
    late_failure;

    companion object {
        val SETTLED = listOf(SubmissionState.success, SubmissionState.permanent_failure, SubmissionState.late_failure)
        val PENDING = listOf(SubmissionState.unsubmitted, SubmissionState.pending)
    }

    fun toTransferStatus(): TransferStatusState {
        return when (this) {
            SubmissionState.unsubmitted, SubmissionState.pending -> TransferStatusState.pending
            SubmissionState.transient_failure -> TransferStatusState.transient_failure
            SubmissionState.permanent_failure -> TransferStatusState.permanent_failure
            SubmissionState.success, SubmissionState.late_failure -> TransferStatusState.success
        }
    }
}

/** Collects database connection steps and any operation on the Nexus tables */
class Database(dbConfig: DatabaseConfig, val currency: String): DbPool(dbConfig, "libeufin_nexus") {
    val payment = PaymentDAO(this)
    val initiated = InitiatedDAO(this)
    val exchange = ExchangeDAO(this)
    val ebics = EbicsDAO(this)
    val list = ListDAO(this)
    val kv = KvDAO(this)
    val transfer = TransferDAO(this)

    private val outgoingTxFlows: MutableSharedFlow<Long> = MutableSharedFlow()
    private val incomingTxFlows: MutableSharedFlow<Long> = MutableSharedFlow()
    private val revenueTxFlows: MutableSharedFlow<Long> = MutableSharedFlow()

    init {
        watchNotifications(pgSource, "libeufin_nexus", LoggerFactory.getLogger("libeufin-nexus-db-watcher"), mapOf(
            "nexus_revenue_tx" to {
                val id = it.toLong()
                revenueTxFlows.emit(id)
            },
            "nexus_outgoing_tx" to {
                val id = it.toLong()
                outgoingTxFlows.emit(id)
            },
            "nexus_incoming_tx" to {
                val id = it.toLong()
                incomingTxFlows.emit(id)
            }
        ))
    }

    /** Listen for new taler outgoing transactions */
    suspend fun <R> listenOutgoing(lambda: suspend (Flow<Long>) -> R): R
        = lambda(outgoingTxFlows)
    /** Listen for new taler incoming transactions */
    suspend fun <R> listenIncoming(lambda: suspend (Flow<Long>) -> R): R
        = lambda(incomingTxFlows)
    /** Listen for new incoming transactions */
    suspend fun <R> listenRevenue(lambda: suspend (Flow<Long>) -> R): R
        = lambda(revenueTxFlows)
}