/*
 * 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.bank.db

import tech.libeufin.bank.*
import tech.libeufin.common.*
import tech.libeufin.common.db.*
import java.time.Instant

/** Data access logic for cashout operations */
class CashoutDAO(private val db: Database) {
    /** Result of cashout operation creation */
    sealed interface CashoutCreationResult {
        data class Success(val id: Long): CashoutCreationResult
        data object UnderMin: CashoutCreationResult
        data object BadConversion: CashoutCreationResult
        data object AccountNotFound: CashoutCreationResult
        data object AccountIsExchange: CashoutCreationResult
        data object BalanceInsufficient: CashoutCreationResult
        data object RequestUidReuse: CashoutCreationResult
        data object NoCashoutPayto: CashoutCreationResult
        data object TanRequired: CashoutCreationResult
    }

    /** Create a new cashout operation */
    suspend fun create(
        username: String,
        requestUid: ShortHashCode,
        amountDebit: TalerAmount,
        amountCredit: TalerAmount,
        subject: String,
        timestamp: Instant,
        is2fa: Boolean
    ): CashoutCreationResult = db.serializable(
        """
        SELECT
            out_bad_conversion,
            out_account_not_found,
            out_account_is_exchange,
            out_balance_insufficient,
            out_request_uid_reuse,
            out_no_cashout_payto,
            out_tan_required,
            out_cashout_id,
            out_under_min
        FROM cashout_create(?,?,(?,?)::taler_amount,(?,?)::taler_amount,?,?,?)
        """
    ) {
        bind(username)
        bind(requestUid)
        bind(amountDebit)
        bind(amountCredit)
        bind(subject)
        bind(timestamp)
        bind(is2fa)
        one {
            when {
                it.getBoolean("out_under_min") -> CashoutCreationResult.UnderMin
                it.getBoolean("out_bad_conversion") -> CashoutCreationResult.BadConversion
                it.getBoolean("out_account_not_found") -> CashoutCreationResult.AccountNotFound
                it.getBoolean("out_account_is_exchange") -> CashoutCreationResult.AccountIsExchange
                it.getBoolean("out_balance_insufficient") -> CashoutCreationResult.BalanceInsufficient
                it.getBoolean("out_request_uid_reuse") -> CashoutCreationResult.RequestUidReuse
                it.getBoolean("out_no_cashout_payto") -> CashoutCreationResult.NoCashoutPayto
                it.getBoolean("out_tan_required") -> CashoutCreationResult.TanRequired
                else -> CashoutCreationResult.Success(it.getLong("out_cashout_id"))
            }
        }
    }

    /** Get status of cashout operation [id] owned by [username] */
    suspend fun get(id: Long, username: String): CashoutStatusResponse? = db.serializable(
        """
        SELECT
            (amount_debit).val as amount_debit_val
            ,(amount_debit).frac as amount_debit_frac
            ,(amount_credit).val as amount_credit_val
            ,(amount_credit).frac as amount_credit_frac
            ,cashout_operations.subject
            ,creation_time
            ,transaction_date as confirmation_date
        FROM cashout_operations
            JOIN bank_accounts ON bank_account=bank_account_id
            JOIN customers ON owning_customer_id=customer_id
            LEFT JOIN bank_account_transactions ON local_transaction=bank_transaction_id
        WHERE cashout_id=? AND username=?
        """
    ) {
        bind(id)
        bind(username)
        oneOrNull {
            CashoutStatusResponse(
                amount_debit = it.getAmount("amount_debit", db.bankCurrency),
                amount_credit = it.getAmount("amount_credit", db.fiatCurrency!!),
                subject = it.getString("subject"),
                creation_time = it.getTalerTimestamp("creation_time"),
                confirmation_time = when (val timestamp = it.getLong("confirmation_date")) {
                    0L -> null
                    else -> TalerTimestamp(timestamp.asInstant())
                },
            )
        }
    }

    /** Get a page of all cashout operations */
    suspend fun pageAll(params: PageParams): List<GlobalCashoutInfo> =
        db.page(params, "cashout_id", """
            SELECT cashout_id, username
            FROM cashout_operations
                JOIN bank_accounts ON bank_account=bank_account_id
                JOIN customers ON owning_customer_id=customer_id
            WHERE
        """) {
            GlobalCashoutInfo(
                cashout_id = it.getLong("cashout_id"),
                username = it.getString("username"),
                status = CashoutStatus.confirmed
            )
        }

    /** Get a page of all cashout operations owned by [username] */
    suspend fun pageForUser(params: PageParams, username: String): List<CashoutInfo> =
        db.page(params, "cashout_id", """
            SELECT cashout_id
            FROM cashout_operations
            WHERE bank_account=(
                SELECT bank_account_id 
                FROM bank_accounts JOIN customers ON owning_customer_id=customer_id 
                WHERE deleted_at IS NULL AND username = ?
            ) AND 
        """, 
            args = { bind(username) }
        ) {
            CashoutInfo(
                cashout_id = it.getLong("cashout_id"),
                status = CashoutStatus.confirmed
            )
        }
}