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

import org.junit.Test
import org.postgresql.jdbc.PgConnection
import tech.libeufin.common.*
import tech.libeufin.common.test.*
import tech.libeufin.nexus.*
import tech.libeufin.nexus.cli.*
import kotlin.math.max

class Bench {

    /** Generate [amount] rows to fill the database */
    fun genData(conn: PgConnection, amount: Int) {
        val amount = max(amount, 10)
        val token32 = ByteArray(32)
        val token64 = ByteArray(64)

        conn.genData(amount, sequenceOf(
            "incoming_transactions(amount, subject, execution_time, debit_payto, bank_id)" to {
                "(42,0)\tsubject\t0\tdebit_payto\tBANK_ID${it*2}\n" + 
                "(42,0)\tsubject\t0\tdebit_payto\tBANK_ID${it*2+1}\n" + 
                "(42,0)\tsubject\t0\tdebit_payto\t\\N\n"
            },
            "outgoing_transactions(amount, subject, execution_time, credit_payto, end_to_end_id)" to {
                "(42,0)\tsubject\t0\tcredit_payto\tE2E_ID${it*2}\n" + 
                "(42,0)\tsubject\t0\tcredit_payto\tE2E_ID${it*2+1}\n"
            },
            "initiated_outgoing_transactions(amount, subject, initiation_time, credit_payto, outgoing_transaction_id, end_to_end_id)" to {
                "(42,0)\tsubject\t0\tcredit_payto\t${it*2}\tE2E_ID$it\n"
            },
            "talerable_incoming_transactions(type, reserve_public_key, account_pub, incoming_transaction_id)" to {
                val hex = token32.rand().encodeHex()
                if (it % 2 == 0) {
                    "reserve\t\\\\x$hex\t\\N\t${it*2}\n"
                } else {
                    "kyc\t\\N\t\\\\x$hex\t${it*2}\n"
                }
            },
            "talerable_outgoing_transactions(wtid, exchange_base_url, outgoing_transaction_id)" to {
                val hex = token32.rand().encodeHex()
                "\\\\x$hex\turl\t${it*2-1}\n"
            },
            "transfer_operations(initiated_outgoing_transaction_id, request_uid, wtid, exchange_base_url)" to {
                val hex32 = token32.rand().encodeHex()
                val hex64 = token64.rand().encodeHex()
                "$it\t\\\\x$hex64\t\\\\x$hex32\turl\n"
            }
        ))
    }

    @Test
    fun benchDb() {
        val ITER = System.getenv("BENCH_ITER")?.toIntOrNull() ?: 0
        val AMOUNT = System.getenv("BENCH_AMOUNT")?.toIntOrNull() ?: 0

        if (ITER == 0) {
            println("Skip benchmark, missing BENCH_ITER")
            return
        }
        println("Bench $ITER times with $AMOUNT rows")

        val ingestCfg = NexusIngestConfig.default(AccountType.exchange)

        bench(ITER) { serverSetup { db ->
            // Generate data
            db.conn { genData(it, AMOUNT) }

            // Warm HTTP client
            client.getA("/taler-revenue/config").assertOk()
            
            // Register
            measureAction("register_in") {
                registerIn(db)
            }
            measureAction("register_out") {
                registerOut(db)
            }
            measureAction("register_reserve") {
                talerableIn(db)
            }
            measureAction("register_kyc") {
                talerableKycIn(db)
            }
            measureAction("register_reserve_missing_id") {
                val incoming = genInPay("test with ${ShortHashCode.rand()} reserve pub")
                registerIncomingPayment(db, ingestCfg, incoming.copy(bankId = null))
                registerIncomingPayment(db, ingestCfg, incoming)
            }
            measureAction("register_kyc_missing_id") {
                val incoming = genInPay("test with KYC:${ShortHashCode.rand()} account pub")
                registerIncomingPayment(db, ingestCfg, incoming.copy(bankId = null))
                registerIncomingPayment(db, ingestCfg, incoming)
            }

            // Revenue API
            measureAction("transaction_revenue") {
                client.getA("/taler-revenue/history").assertOk()
            }

            // Wire gateway
            measureAction("wg_transfer") {
                client.postA("/taler-wire-gateway/transfer") {
                    json { 
                        "request_uid" to HashCode.rand()
                        "amount" to "CHF:0.0001"
                        "exchange_base_url" to "http://exchange.example.com/"
                        "wtid" to ShortHashCode.rand()
                        "credit_account" to grothoffPayto
                    }
                }.assertOk()
            }
            measureAction("wg_transfer_get") {
                client.getA("/taler-wire-gateway/transfers/42").assertOk()
            }
            measureAction("wg_transfer_page") {
                client.getA("/taler-wire-gateway/transfers").assertOk()
            }
            measureAction("wg_transfer_page_filter") {
                client.getA("/taler-wire-gateway/transfers?status=success").assertNoContent()
            }
            measureAction("wg_add") {
                client.postA("/taler-wire-gateway/admin/add-incoming") {
                    json { 
                        "amount" to "CHF:0.0001"
                        "reserve_pub" to EddsaPublicKey.rand()
                        "debit_account" to grothoffPayto
                    }
                }.assertOk()
            }
            measureAction("wg_incoming") {
                client.getA("/taler-wire-gateway/history/incoming")
                    .assertOk()
            }
            measureAction("wg_outgoing") {
                client.getA("/taler-wire-gateway/history/outgoing")
                    .assertOk()
            }
        }}
    }
}