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

import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.server.testing.*
import org.junit.Test
import tech.libeufin.common.*
import tech.libeufin.common.crypto.CryptoUtil
import tech.libeufin.nexus.*
import tech.libeufin.nexus.cli.*
import tech.libeufin.ebics.randEbicsId
import java.time.Instant
import kotlin.test.*

class WireTransferApiTest {
    // GET /taler-wire-transfer-gateway/config
    @Test
    fun config() = serverSetup {
        client.get("/taler-wire-transfer-gateway/config").assertOkJson<WireTransferConfig>()
    }

    // POST /taler-wire-transfer-gateway/registration
    @Test
    fun registration() = serverSetup { db ->
        val (priv, pub) = EddsaPublicKey.randEdsaKeyPair()
        val amount = TalerAmount("KUDOS:55")
        val valid_req = obj {
            "credit_amount" to amount
            "type" to "reserve"
            "alg" to "ECDSA"
            "account_pub" to pub
            "authorization_pub" to pub
            "authorization_sig" to CryptoUtil.eddsaSign(pub.raw, priv)
            "recurrent" to false
        }

        val subjects = listOf(
            TransferSubject.QrBill(subjectFmtQrBill(pub), amount),
            TransferSubject.Simple("Taler MAP:$pub", amount),
        )

        // Valid
        client.post("/taler-wire-transfer-gateway/registration") {
            json(valid_req)
        }.assertOkJson<SubjectResult> {
            assertEquals(it.subjects, subjects)
        }

        // Idempotent
        client.post("/taler-wire-transfer-gateway/registration") {
            json(valid_req)
        }.assertOkJson<SubjectResult> {
            assertEquals(it.subjects, subjects)
        }

        // Bad signature
        client.post("/taler-wire-transfer-gateway/registration") {
            json(valid_req) {
                "authorization_sig" to EddsaSignature.rand()
            }
        }.assertConflict(TalerErrorCode.BANK_BAD_SIGNATURE)

        // Check authorization field in incoming history
        val (testPriv, testAuth) = EddsaPublicKey.randEdsaKeyPair()
        val testKey = EddsaPublicKey.randEdsaKey()
        val testSig = CryptoUtil.eddsaSign(testKey.raw, testPriv)
        val qr = subjectFmtQrBill(testAuth)
        client.post("/taler-wire-transfer-gateway/registration") {
            json(valid_req) {
                "account_pub" to testKey
                "authorization_pub" to testAuth
                "authorization_sig" to testSig
                "recurrent" to true
            }
        }.assertOkJson<SubjectResult>()
        val cfg = NexusIngestConfig.default(AccountType.exchange)
        registerIncomingPayment(db, cfg, genInPay(qr))
        registerIncomingPayment(db, cfg, genInPay(qr))
        registerIncomingPayment(db, cfg, genInPay(qr))
        client.post("/taler-wire-transfer-gateway/registration") {
            json(valid_req) {
                "type" to "kyc"
                "account_pub" to testKey
                "authorization_pub" to testAuth
                "authorization_sig" to testSig
                "recurrent" to true
            }
        }.assertOkJson<SubjectResult>()
        val otherPub = EddsaPublicKey.randEdsaKey()
        val otherSig = CryptoUtil.eddsaSign(otherPub.raw, testPriv)
        client.post("/taler-wire-transfer-gateway/registration") {
            json(valid_req) {
                "type" to "reserve"
                "account_pub" to otherPub
                "authorization_pub" to testAuth
                "authorization_sig" to otherSig
                "recurrent" to true
            }
        }.assertOkJson<SubjectResult>()
        val lastPub = EddsaPublicKey.randEdsaKey()
        talerableIn(db, reserve_pub=lastPub)
        talerableKycIn(db, account_pub=lastPub)
        val history = client.getA("/taler-wire-gateway/history/incoming?limit=-5")
            .assertOkJson<IncomingHistory>().incoming_transactions.map {
                when (it) {
                    is IncomingKycAuthTransaction -> Triple(it.account_pub, it.authorization_pub, it.authorization_sig)
                    is IncomingReserveTransaction -> Triple(it.reserve_pub, it.authorization_pub, it.authorization_sig)
                    else -> throw UnsupportedOperationException()
                }
            }
        assertContentEquals(history, listOf(
            Triple(lastPub, null, null),
            Triple(lastPub, null, null),
            Triple(otherPub, testAuth, otherSig),
            Triple(testKey, testAuth, testSig),
            Triple(testKey, testAuth, testSig)
        ))
    }

    // DELETE /taler-wire-transfer-gateway/registration
    @Test
    fun unregistration() = serverSetup {
        val (priv, pub) = EddsaPublicKey.randEdsaKeyPair()

        // Unknown
        client.delete("/taler-wire-transfer-gateway/registration") {
            val now = Instant.now().toString()
            json {
                "timestamp" to now
                "authorization_pub" to pub
                "authorization_sig" to CryptoUtil.eddsaSign(now.toByteArray(), priv)
            }
        }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)
                
        // Know
        client.post("/taler-wire-transfer-gateway/registration") {
            json {
                "credit_amount" to "KUDOS:55"
                "type" to "reserve"
                "alg" to "ECDSA"
                "account_pub" to pub
                "authorization_pub" to pub
                "authorization_sig" to CryptoUtil.eddsaSign(pub.raw, priv)
                "recurrent" to false
            }
        }.assertOkJson<SubjectResult>()
        client.delete("/taler-wire-transfer-gateway/registration") {
            val now = Instant.now().toString()
            json {
                "timestamp" to now
                "authorization_pub" to pub
                "authorization_sig" to CryptoUtil.eddsaSign(now.toByteArray(), priv)
            }
        }.assertNoContent()

        // Idempotent
        client.delete("/taler-wire-transfer-gateway/registration") {
            val now = Instant.now().toString()
            json {
                "timestamp" to now
                "authorization_pub" to pub
                "authorization_sig" to CryptoUtil.eddsaSign(now.toByteArray(), priv)
            }
        }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)

        // Bad signature
        client.delete("/taler-wire-transfer-gateway/registration") {
            val now = Instant.now().toString()
            json {
                "timestamp" to now
                "authorization_pub" to pub
                "authorization_sig" to CryptoUtil.eddsaSign("lol".toByteArray(), priv)
            }
        }.assertConflict(TalerErrorCode.BANK_BAD_SIGNATURE)

        // Old timestamp
        client.delete("/taler-wire-transfer-gateway/registration") {
            val now = Instant.now().minusSeconds(1000000).toString()
            json {
                "timestamp" to now
                "authorization_pub" to pub
                "authorization_sig" to CryptoUtil.eddsaSign(now.toByteArray(), priv)
            }
        }.assertConflict(TalerErrorCode.BANK_OLD_TIMESTAMP)
    }
}