/*
 * 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 org.junit.Test
import tech.libeufin.common.*
import tech.libeufin.common.crypto.CryptoUtil
import tech.libeufin.nexus.*
import tech.libeufin.nexus.cli.*
import java.time.Instant
import kotlin.test.*

class PreparedTransferApiTest {
    // GET /taler-prepared-transfer/config
    @Test
    fun config() = serverSetup {
        client.get("/taler-prepared-transfer/config").assertOkJson<PreparedTransferConfig>()
    }

    // POST /taler-prepared-transfer/registration
    @Test
    fun registration() = serverSetup { db ->
        val (priv, pub) = EddsaPublicKey.randEdsaKeyPair()
        val amount = TalerAmount("KUDOS:55")
        val valid_req = SubjectRequest(
            Payto.parse("payto://iban/CH7789144474425692816"),
            TransferType.reserve,
            false,
            amount,
            PublicKeyAlg.EdDSA,
            pub,
            pub,
            EddsaSignature.rand()
        )

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

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

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

        // Valid qr
        client.post("/taler-prepared-transfer/registration") {
            json(valid_req.copy(credit_account=Payto.parse("payto://iban/CH4431999123000889012")).sign(priv))
        }.assertOkJson<SubjectResult> {
            assertEquals(it.subjects, qrs)
        }

        // Idempotent qr
        client.post("/taler-prepared-transfer/registration") {
            json(valid_req.copy(credit_account=Payto.parse("payto://iban/CH4431999123000889012")).sign(priv))
        }.assertOkJson<SubjectResult> {
            assertEquals(it.subjects, qrs)
        }

        // Bad signature
        client.post("/taler-prepared-transfer/registration") {
            json(valid_req)
        }.assertForbidden(TalerErrorCode.BANK_BAD_SIGNATURE)

        // Unknown account
        client.post("/taler-prepared-transfer/registration") {
            json(valid_req.copy(credit_account=Payto.parse(grothoffPayto)).sign(priv))
        }.assertConflict(TalerErrorCode.BANK_UNKNOWN_CREDITOR)

        // Check authorization field in incoming history
        val (testPriv, testAuth) = EddsaPublicKey.randEdsaKeyPair()
        val testKey = EddsaPublicKey.randEdsaKey()
        val qr = subjectFmtQrBill(testAuth)
        client.post("/taler-prepared-transfer/registration") {
            json(valid_req.copy(
                credit_account=Payto.parse("payto://iban/CH4431999123000889012"),
                account_pub=testKey,
                authorization_pub=testAuth,
                recurrent=true
            ).sign(testPriv))
        }.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-prepared-transfer/registration") {
            json(valid_req.copy(
                type=TransferType.kyc,
                account_pub=testKey,
                authorization_pub=testAuth,
                recurrent=true
            ).sign(testPriv))
        }.assertOkJson<SubjectResult>()
        val otherPub = EddsaPublicKey.randEdsaKey()
        client.post("/taler-prepared-transfer/registration") {
            json(valid_req.copy(
                account_pub=otherPub,
                authorization_pub=testAuth,
                recurrent=true
            ).sign(testPriv))
        }.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 -> Pair(it.account_pub, it.authorization_pub)
                    is IncomingReserveTransaction -> Pair(it.reserve_pub, it.authorization_pub)
                    else -> throw UnsupportedOperationException()
                }
            }
        assertContentEquals(history, listOf(
            Pair(lastPub, null),
            Pair(lastPub, null),
            Pair(otherPub, testAuth),
            Pair(testKey, testAuth),
            Pair(testKey, testAuth)
        ))
    }

    // DELETE /taler-prepared-transfer/registration
    @Test
    fun unregistration() = serverSetup {
        val (priv, pub) = EddsaPublicKey.randEdsaKeyPair()
        val now = TalerTimestamp(Instant.now())
        val req = Unregistration(
            now,
            pub,
            EddsaSignature.rand()
        ).sign(priv)
        
        // Unknown
        client.post("/taler-prepared-transfer/unregistration") {
            json(req)
        }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)

        // Know
        client.post("/taler-prepared-transfer/registration") {
            json(SubjectRequest(
                Payto.parse("payto://iban/CH7789144474425692816"),
                TransferType.reserve,
                false,
                TalerAmount("KUDOS:55"),
                PublicKeyAlg.EdDSA,
                pub,
                pub,
                EddsaSignature.rand()
            ).sign(priv))
        }.assertOkJson<SubjectResult>()
        client.post("/taler-prepared-transfer/unregistration") {
            json(req)
        }.assertNoContent()

        // Idempotent
        client.post("/taler-prepared-transfer/unregistration") {
            json(req)
        }.assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND)

        // Bad signature
        client.post("/taler-prepared-transfer/unregistration") {
            json(Unregistration(
                now,
                pub,
                EddsaSignature.rand()
            ))
        }.assertForbidden(TalerErrorCode.BANK_BAD_SIGNATURE)

        // Old timestamp
        client.post("/taler-prepared-transfer/unregistration") {
            json(
                Unregistration(
                    TalerTimestamp(Instant.now().minusSeconds(1000000)),
                    pub,
                    EddsaSignature.rand()
                ).sign(priv)
            )
        }.assertConflict(TalerErrorCode.BANK_OLD_TIMESTAMP)
    }
}