/*
 * This file is part of LibEuFin.
 * Copyright (C) 2024, 2025, 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.application.*
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.nexus.NexusConfig
import tech.libeufin.nexus.checkCurrency
import tech.libeufin.nexus.db.Database
import tech.libeufin.nexus.db.ExchangeDAO
import tech.libeufin.nexus.db.ExchangeDAO.TransferResult
import tech.libeufin.nexus.db.PaymentDAO.IncomingRegistrationResult
import tech.libeufin.nexus.iso20022.*
import tech.libeufin.ebics.randEbicsId
import java.time.Instant


fun Routing.wireGatewayApi(db: Database, cfg: NexusConfig) = conditional(cfg.wireGatewayApiCfg) {
    get("/taler-wire-gateway/config", {
        operationId = "getWireGatewayConfig"
        description = "Get the configuration of the wire gateway"
        tags = listOf("Wire Gateway")
        response {
            code(HttpStatusCode.OK) { description = "Configuration of the wire gateway"; body<WireGatewayConfig>() }
        }
    }) {
        call.respond(
            WireGatewayConfig(
                currency = cfg.currency,
                support_account_check = true
            )
        )
    }
    auth(cfg.wireGatewayApiCfg) {
        post("/taler-wire-gateway/transfer", {
            operationId = "createTransfer"
            description = "Initiate a wire transfer"
            tags = listOf("Wire Gateway")
            protected = true
            securitySchemeNames("bearerAuth", "basicAuth")
            request {
                body<TransferRequest>()
            }
            response {
                code(HttpStatusCode.OK) { description = "Transfer initiated successfully"; body<TransferResponse>() }
                code(HttpStatusCode.Conflict) { description = "Request UID or WTID already used" }
            }
        }) {
            val req = call.receive<TransferRequest>()
            cfg.checkCurrency(req.amount)
            req.credit_account.expectIbanFull()
            val endToEndId = randEbicsId()
            val res = db.exchange.transfer(
                req,
                endToEndId,
                Instant.now()
            )
            when (res) {
                TransferResult.RequestUidReuse -> throw conflict(
                    "request_uid used already",
                    TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED
                )

                TransferResult.WtidReuse -> throw conflict(
                    "wtid used already",
                    TalerErrorCode.BANK_TRANSFER_WTID_REUSED
                )

                is TransferResult.Success -> call.respond(
                    TransferResponse(
                        timestamp = res.timestamp,
                        row_id = res.id
                    )
                )
            }
        }
        get("/taler-wire-gateway/transfers", {
            operationId = "listTransfers"
            description = "List wire transfers"
            tags = listOf("Wire Gateway")
            protected = true
            securitySchemeNames("bearerAuth", "basicAuth")
            request {
                queryParameter<Long>("start") { description = "Row ID to start from (legacy alias for offset)"; required = false }
                queryParameter<Long>("offset") { description = "Row ID to start from"; required = false }
                queryParameter<Int>("delta") { description = "Number of results to return (legacy alias for limit)"; required = false }
                queryParameter<Int>("limit") { description = "Number of results to return. Negative for descending order. Max 1024"; required = false }
                queryParameter<String>("status") { description = "Filter by transfer status"; required = false }
            }
            response {
                code(HttpStatusCode.OK) { description = "List of transfers"; body<TransferList>() }
                code(HttpStatusCode.NoContent) { description = "No transfers found" }
            }
        }) {
            val params = TransferParams.extract(call.request.queryParameters)
            val items = db.exchange.pageTransfer(params)

            if (items.isEmpty()) {
                call.respond(HttpStatusCode.NoContent)
            } else {
                call.respond(TransferList(items, cfg.ebics.payto))
            }
        }
        get("/taler-wire-gateway/transfers/{ROW_ID}", {
            operationId = "getTransfer"
            description = "Get details of a specific wire transfer"
            tags = listOf("Wire Gateway")
            protected = true
            securitySchemeNames("bearerAuth", "basicAuth")
            request {
                pathParameter<Long>("ROW_ID") { description = "Row ID of the transfer" }
            }
            response {
                code(HttpStatusCode.OK) { description = "Transfer details"; body<TransferStatus>() }
                code(HttpStatusCode.NotFound) { description = "Transfer not found" }
            }
        }) {
            val id = call.longPath("ROW_ID")
            val transfer = db.exchange.getTransfer(id) ?: throw notFound(
                "Transfer '$id' not found",
                TalerErrorCode.BANK_TRANSACTION_NOT_FOUND
            )
            call.respond(transfer)
        }
        suspend fun <T> ApplicationCall.historyEndpoint(
            reduce: (List<T>, String) -> Any,
            dbLambda: suspend ExchangeDAO.(HistoryParams) -> List<T>
        ) {
            val params = HistoryParams.extract(this.request.queryParameters)
            val items = db.exchange.dbLambda(params)
            if (items.isEmpty()) {
                this.respond(HttpStatusCode.NoContent)
            } else {
                this.respond(reduce(items, cfg.ebics.payto))
            }
        }
        get("/taler-wire-gateway/history/incoming", {
            operationId = "getIncomingHistory"
            description = "Get incoming transaction history"
            tags = listOf("Wire Gateway")
            protected = true
            securitySchemeNames("bearerAuth", "basicAuth")
            request {
                queryParameter<Long>("start") { description = "Row ID to start from (legacy alias for offset)"; required = false }
                queryParameter<Long>("offset") { description = "Row ID to start from"; required = false }
                queryParameter<Int>("delta") { description = "Number of results to return (legacy alias for limit)"; required = false }
                queryParameter<Int>("limit") { description = "Number of results to return. Negative for descending order. Max 1024"; required = false }
                queryParameter<Long>("long_poll_ms") { description = "Long-polling timeout in milliseconds (legacy alias for timeout_ms)"; required = false }
                queryParameter<Long>("timeout_ms") { description = "Long-polling timeout in milliseconds. Max 3600000 (1 hour)"; required = false }
            }
            response {
                code(HttpStatusCode.OK) { description = "Incoming transaction history"; body<IncomingHistory>() }
                code(HttpStatusCode.NoContent) { description = "No incoming transactions found" }
            }
        }) {
            call.historyEndpoint(::IncomingHistory, ExchangeDAO::incomingHistory)
        }
        get("/taler-wire-gateway/history/outgoing", {
            operationId = "getOutgoingHistory"
            description = "Get outgoing transaction history"
            tags = listOf("Wire Gateway")
            protected = true
            securitySchemeNames("bearerAuth", "basicAuth")
            request {
                queryParameter<Long>("start") { description = "Row ID to start from (legacy alias for offset)"; required = false }
                queryParameter<Long>("offset") { description = "Row ID to start from"; required = false }
                queryParameter<Int>("delta") { description = "Number of results to return (legacy alias for limit)"; required = false }
                queryParameter<Int>("limit") { description = "Number of results to return. Negative for descending order. Max 1024"; required = false }
                queryParameter<Long>("long_poll_ms") { description = "Long-polling timeout in milliseconds (legacy alias for timeout_ms)"; required = false }
                queryParameter<Long>("timeout_ms") { description = "Long-polling timeout in milliseconds. Max 3600000 (1 hour)"; required = false }
            }
            response {
                code(HttpStatusCode.OK) { description = "Outgoing transaction history"; body<OutgoingHistory>() }
                code(HttpStatusCode.NoContent) { description = "No outgoing transactions found" }
            }
        }) {
            call.historyEndpoint(::OutgoingHistory, ExchangeDAO::outgoingHistory)
        }
        get("/taler-wire-gateway/account/check", {
            operationId = "checkAccount"
            description = "Check account status"
            tags = listOf("Wire Gateway")
            protected = true
            securitySchemeNames("bearerAuth", "basicAuth")
            response {
                code(HttpStatusCode.NotImplemented) { description = "Not implemented" }
            }
        }) {
            throw notImplemented()
        }
        suspend fun ApplicationCall.addIncoming(
            amount: TalerAmount,
            debitAccount: Payto,
            subject: String,
            metadata: IncomingSubject
        ) {
            cfg.checkCurrency(amount)
            val debitAccount = debitAccount.expectIban()
            val timestamp = Instant.now()
            val res = db.payment.registerTalerableIncoming(
                IncomingPayment(
                    amount = amount,
                    debtor = debitAccount,
                    subject = subject,
                    executionTime = timestamp,
                    id = IncomingId(null, randEbicsId(), null)
                ), metadata
            )
            when (res) {
                IncomingRegistrationResult.ReservePubReuse -> throw conflict(
                    "reserve_pub used already",
                    TalerErrorCode.BANK_DUPLICATE_RESERVE_PUB_SUBJECT
                )
                IncomingRegistrationResult.MappingReuse -> throw conflict(
                    "authorization_pub used already",
                    TalerErrorCode.BANK_TRANSFER_MAPPING_REUSED
                )
                IncomingRegistrationResult.UnknownMapping -> throw conflict(
                    "authorization_pub unknown",
                    TalerErrorCode.BANK_TRANSFER_MAPPING_UNKNOWN
                )
                is IncomingRegistrationResult.Success -> respond(
                    AddIncomingResponse(
                        timestamp = TalerTimestamp(timestamp),
                        row_id = res.id
                    )
                )
            }
        }
        post("/taler-wire-gateway/admin/add-incoming", {
            operationId = "addIncoming"
            description = "Manually add an incoming transaction"
            tags = listOf("Wire Gateway")
            protected = true
            securitySchemeNames("bearerAuth", "basicAuth")
            request {
                body<AddIncomingRequest>()
            }
            response {
                code(HttpStatusCode.OK) { description = "Incoming transaction added"; body<AddIncomingResponse>() }
                code(HttpStatusCode.Conflict) { description = "Reserve pub already used" }
            }
        }) {
            val req = call.receive<AddIncomingRequest>()
            call.addIncoming(
                amount = req.amount,
                debitAccount = req.debit_account,
                subject = "Manual incoming ${req.reserve_pub}",
                metadata = IncomingSubject.Reserve(req.reserve_pub)
            )
        }
        post("/taler-wire-gateway/admin/add-kycauth", {
            operationId = "addKycauth"
            description = "Manually add a KYC auth incoming transaction"
            tags = listOf("Wire Gateway")
            protected = true
            securitySchemeNames("bearerAuth", "basicAuth")
            request {
                body<AddKycauthRequest>()
            }
            response {
                code(HttpStatusCode.OK) { description = "KYC auth incoming transaction added"; body<AddIncomingResponse>() }
                code(HttpStatusCode.Conflict) { description = "Reserve pub already used" }
            }
        }) {
            val req = call.receive<AddKycauthRequest>()
            call.addIncoming(
                amount = req.amount,
                debitAccount = req.debit_account,
                subject = "Manual incoming KYC:${req.account_pub}",
                metadata = IncomingSubject.Kyc(req.account_pub)
            )
        }
        post("/taler-wire-gateway/admin/add-mapped") {
            val req = call.receive<AddMappedRequest>()
            call.addIncoming(
                amount = req.amount,
                debitAccount = req.debit_account,
                subject = "Manual incoming MAP:${req.authorization_pub}",
                metadata = IncomingSubject.Map(req.authorization_pub)
            )
        }
    }
}
