/*
 * 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/>
 */

package tech.libeufin.nexus.api

import io.ktor.http.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.github.smiley4.ktoropenapi.*
import io.ktor.util.pipeline.*
import tech.libeufin.common.*
import tech.libeufin.common.crypto.CryptoUtil
import tech.libeufin.nexus.NexusConfig
import tech.libeufin.nexus.db.Database
import tech.libeufin.nexus.db.TransferDAO.RegistrationResult
import java.time.Instant
import java.time.Duration

fun Routing.preparedTransferAPI(db: Database, cfg: NexusConfig) = conditional(cfg.wireGatewayApiCfg) {
    get("/taler-prepared-transfer/config", {
        operationId = "getPreparedTransferConfig"
        description = "Get the configuration of the prepared transfer API"
        tags = listOf("Prepared Transfer")
        response {
            code(HttpStatusCode.OK) { description = "Configuration of the prepared transfer API"; body<PreparedTransferConfig>() }
        }
    }) {
        call.respond(
            PreparedTransferConfig(
                currency = cfg.currency,
                supported_formats = listOf(SubjectFormat.SIMPLE, SubjectFormat.CH_QR_BILL)
            )
        )
    }
    post("/taler-prepared-transfer/registration", {
        operationId = "registerTransfer"
        description = "Register a prepared transfer"
        tags = listOf("Prepared Transfer")
        request {
            body<SubjectRequest>()
        }
        response {
            code(HttpStatusCode.OK) { description = "Registration successful"; body<SubjectResult>() }
            code(HttpStatusCode.Conflict) { description = "Reserve pub or subject derivation already used" }
        }
    }) {
        val req = call.receive<SubjectRequest>();

        if (!req.verify())
            throw forbidden(
                "invalid signature",
                TalerErrorCode.BANK_BAD_SIGNATURE
            )

        val iban = req.credit_account.expectIban().iban
        val reference = if (iban == cfg.ebics.account.iban) {
            null
        } else if (iban == cfg.ebics.qrIban) {
            subjectFmtQrBill(req.authorization_pub)
        } else {
            throw conflict(
                "Creditor account '${req.credit_account}' is not supported",
                TalerErrorCode.BANK_UNKNOWN_CREDITOR
            )
        }

        when (val result = db.transfer.register(
            req.type,
            req.account_pub,
            req.authorization_pub,
            req.authorization_sig,
            req.recurrent,
            reference,
            Instant.now()
        )) {
            RegistrationResult.ReservePubReuse -> throw conflict(
                "reserve_pub used already",
                TalerErrorCode.BANK_DUPLICATE_RESERVE_PUB_SUBJECT
            )
            RegistrationResult.SubjectReuse ->  throw conflict(
                "subject derivation used already",
                TalerErrorCode.BANK_DERIVATION_REUSE
            )
            RegistrationResult.Success -> {
                call.respond(
                    SubjectResult(
                        if (reference != null) {
                            listOf(TransferSubject.QrBill(reference, req.credit_amount))
                        } else {
                            listOf(TransferSubject.Simple(fmtIncomingSubject(IncomingType.map, req.authorization_pub), req.credit_amount))
                        },
                        TalerTimestamp.never()
                    )
                )
            }
        }
    }
    post("/taler-prepared-transfer/unregistration", {
        operationId = "unregisterTransfer"
        description = "Unregister a prepared subject"
        tags = listOf("Prepared Transfer")
        response {
            code(HttpStatusCode.NoContent) { description = "Successfully unregistered" }
            code(HttpStatusCode.NotFound) { description = "Prepared transfer not found" }
            code(HttpStatusCode.Conflict) { description = "Invalid signature or timestamp too old" }
        }
    }) {
        val req = call.receive<Unregistration>();

        if (req.timestamp.instant.isBefore(Instant.now().minus(Duration.ofMinutes(15))))
            throw conflict(
                "timestamp too old",
                TalerErrorCode.BANK_OLD_TIMESTAMP
            )

        if (!req.verify())
            throw forbidden(
                "invalid signature",
                TalerErrorCode.BANK_BAD_SIGNATURE
            )
                
        if (db.transfer.unregister(req.authorization_pub, Instant.now())) {
            call.respond(HttpStatusCode.NoContent)
        } else {
            throw notFound(
                "Prepared transfer '${req.authorization_pub}' not found",
                TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
            )
        }
    }
}
