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

import tech.libeufin.common.*
import tech.libeufin.common.db.*
import tech.libeufin.nexus.iso20022.*
import java.sql.Types
import java.time.Instant
import java.util.UUID

/** Data access logic for metadata listing */
class ListDAO(private val db: Database) {
    /** List incoming transaction metadata for debugging */
    suspend fun incoming(incomplete: Boolean): List<IncomingTxMetadata> = db.serializable(
        """
        SELECT
             (incoming.amount).val AS amount_val
            ,(incoming.amount).frac AS amount_frac
            ,(credit_fee).val AS credit_fee_val
            ,(credit_fee).frac AS credit_fee_frac
            ,incoming.subject
            ,end_to_end_id AS bounced
            ,execution_time
            ,debit_payto
            ,type
            ,metadata
            ,uetr
            ,tx_id
            ,acct_svcr_ref
            ,talerable_incoming_transactions.authorization_pub as auth_pub
            ,pending_recurrent_incoming_transactions.authorization_pub as pending_pub
        FROM incoming_transactions AS incoming
            LEFT JOIN talerable_incoming_transactions USING (incoming_transaction_id)
            LEFT JOIN bounced_transactions USING (incoming_transaction_id)
            LEFT JOIN initiated_outgoing_transactions USING (initiated_outgoing_transaction_id)
            LEFT JOIN pending_recurrent_incoming_transactions USING (incoming_transaction_id)
        ${if (incomplete) { "WHERE debit_payto IS NULL OR incoming.subject IS NULL" } else { ""}}
        ORDER BY execution_time
        """
    ) {
        all {
            val type = it.getOptEnum<IncomingType>("type")
            val authPub = it.getOptKey("auth_pub")
            val pendingPub = it.getOptKey("pending_pub")
            val map = if (authPub != null) {
                " mapped by ${authPub}"
            } else {
                ""
            }
            IncomingTxMetadata(
                id = IncomingId(
                    it.getObject("uetr") as UUID?,
                    it.getString("tx_id"),
                    it.getString("acct_svcr_ref"),
                ),
                date = it.getLong("execution_time").asInstant(),
                amount = it.getAmount("amount", db.currency),
                creditFee = it.getDecimal("credit_fee"),
                subject = it.getString("subject"),
                debtor = it.getString("debit_payto"),
                bounced = it.getString("bounced"),
                talerable = when (type) {
                    null -> {
                        if (pendingPub != null) {
                            "pending mapped by $pendingPub"
                        } else {
                            null
                        }
                    }
                    IncomingType.reserve -> "reserve ${EddsaPublicKey(it.getBytes("metadata"))}$map"
                    IncomingType.kyc -> "kyc ${EddsaPublicKey(it.getBytes("metadata"))}$map"
                    IncomingType.map -> throw UnsupportedOperationException()
                }
            )
        }
    }

    /** List outgoing transaction metadata for debugging */
    suspend fun outgoing(): List<OutgoingTxMetadata> = db.serializable(
        """
        SELECT
             (amount).val AS amount_val
            ,(amount).frac AS amount_frac
            ,subject
            ,execution_time
            ,credit_payto
            ,end_to_end_id
            ,acct_svcr_ref
            ,wtid
            ,exchange_base_url
        FROM outgoing_transactions
            LEFT JOIN talerable_outgoing_transactions using (outgoing_transaction_id)
        ORDER BY execution_time
        """
    ) {
        all {
            OutgoingTxMetadata(
                id = OutgoingId(
                    msgId = null,
                    endToEndId = it.getString("end_to_end_id"),
                    acctSvcrRef = it.getString("acct_svcr_ref"),
                ),
                date = it.getLong("execution_time").asInstant(),
                amount = it.getAmount("amount", db.currency),
                subject = it.getString("subject"),
                creditor = it.getString("credit_payto"),
                wtid = it.getBytes("wtid")?.run { ShortHashCode(this) },
                exchangeBaseUrl = it.getString("exchange_base_url")
            )
        }
    }

    /** List initiated transaction metadata for debugging */
    suspend fun initiated(): List<InitiatedTxMetadata> = db.serializable(
        """
        SELECT
             (amount).val AS amount_val
            ,(amount).frac AS amount_frac
            ,subject
            ,initiation_time
            ,submission_date
            ,submission_counter
            ,credit_payto
            ,end_to_end_id
            ,message_id
            ,order_id
            ,initiated_outgoing_transactions.status
            ,initiated_outgoing_transactions.status_msg
        FROM initiated_outgoing_transactions
            LEFT JOIN initiated_outgoing_batches USING (initiated_outgoing_batch_id)
        ORDER BY initiation_time
        """
    ) {
        all {
            InitiatedTxMetadata(
                date = it.getLong("initiation_time").asInstant(),
                amount = it.getAmount("amount", db.currency),
                subject = it.getString("subject"),
                creditor = it.getString("credit_payto"),
                id = it.getString("end_to_end_id"),
                batch = it.getString("message_id"),
                batchOrder = it.getString("order_id"),
                status = it.getString("status"),
                msg = it.getString("status_msg"),
                submissionTime = it.getLong("submission_date").asInstant(),
                submissionCounter = it.getInt("submission_counter")
            )
        }
    }
    
    /** List initiated transaction metadata pending acknowledgment for debugging */
    suspend fun initiatedAck(): List<InitiatedTxMetadataAck> = db.serializable(
        """
        SELECT
             (amount).val AS amount_val
            ,(amount).frac AS amount_frac
            ,subject
            ,initiation_time
            ,credit_payto
            ,end_to_end_id
            ,initiated_outgoing_transaction_id
        FROM initiated_outgoing_transactions
        WHERE initiated_outgoing_batch_id IS NULL AND NOT awaiting_ack
        ORDER BY initiation_time
        """
    ) {
        all {
            InitiatedTxMetadataAck(
                date = it.getLong("initiation_time").asInstant(),
                amount = it.getAmount("amount", db.currency),
                subject = it.getString("subject"),
                creditor = it.getString("credit_payto"),
                id = it.getString("end_to_end_id"),
                dbId = it.getLong("initiated_outgoing_transaction_id"),
            )
        }
    }
}

/** Incoming transaction metadata for debugging */
data class IncomingTxMetadata(
    val id: IncomingId,
    val date: Instant,
    val amount: TalerAmount,
    val creditFee: DecimalNumber,
    val subject: String?,
    val debtor: String?,
    val talerable: String?,
    val bounced: String?
)

/** Outgoing transaction metadata for debugging */
data class OutgoingTxMetadata(
    val id: OutgoingId,
    val date: Instant,
    val amount: TalerAmount,
    val subject: String?,
    val creditor: String?,
    val wtid: ShortHashCode?,
    val exchangeBaseUrl: String?
)

/** Initiated metadata for debugging */
data class InitiatedTxMetadata(
    val date: Instant,
    val amount: TalerAmount,
    val subject: String,
    val creditor: String,
    val id: String,
    val batch: String?,
    val batchOrder: String?,
    val status: String,
    val msg: String?,
    val submissionTime: Instant,
    val submissionCounter: Int
)

/** Initiated metadata for debugging */
data class InitiatedTxMetadataAck(
    val date: Instant,
    val amount: TalerAmount,
    val subject: String,
    val creditor: String,
    val dbId: Long,
    val id: String
)