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

import tech.libeufin.common.*
import tech.libeufin.nexus.*
import tech.libeufin.ebics.XmlBuilder
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter

/** String representation of a Taler [amount] compatible with EBICS */
fun getAmountNoCurrency(amount: TalerAmount): String {
    if (amount.isSubCent()) {
        throw Exception("Sub-cent amounts not supported")
    }
    return amount.number().toString()
}

/** pain.001 transaction metadata */
data class Pain001Tx(
    val creditor: IbanAccountMetadata,
    val amount: TalerAmount,
    val subject: String,
    val endToEndId: String
)

/** pain.001 message metadata */
data class Pain001Msg(
    val messageId: String,
    val timestamp: Instant,
    val debtor: IbanAccountMetadata,
    val sum: TalerAmount,
    val txs: List<Pain001Tx>
)

/** Create a pain.001 XML document [msg] valid for [dialect] */
fun createPain001(
    msg: Pain001Msg,
    dialect: Dialect,
    instant: Boolean
): ByteArray {
    val version = "09"
    val suffix = when (dialect.standard()) {
        Standard.SIX -> ".ch.03"
        Standard.GBIC -> ""
    }
    val zonedTimestamp = ZonedDateTime.ofInstant(msg.timestamp, ZoneId.of("UTC"))
    val totalAmount = getAmountNoCurrency(msg.sum)
    return XmlBuilder.toBytes("Document") {
        attr("xmlns", "urn:iso:std:iso:20022:tech:xsd:pain.001.001.$version")
        attr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
        attr("xsi:schemaLocation", "urn:iso:std:iso:20022:tech:xsd:pain.001.001.$version pain.001.001.$version$suffix.xsd")
        el("CstmrCdtTrfInitn") {
            el("GrpHdr") {
                // Used for idempotency as banks will refuse to process EBICS request with the same MsgId for a pre-agreed period
                // Used to uniquely identify batches of transactions in other files
                el("MsgId", msg.messageId) 
                el("CreDtTm", DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(zonedTimestamp))
                el("NbOfTxs", msg.txs.size.toString())
                el("CtrlSum", totalAmount)
                el("InitgPty/Nm", msg.debtor.name)
                /* TODO fail with GLS: ES_VERIFICATION IncorrectFileStructure - 'Signature verification' 'The file format is incomplete or invalid'
                el("InitnSrc") {
                    el("Nm", "LibEuFin")
                    el("Prvdr", "Taler Systems SA")
                    el("Vrsn", VERSION)
                }*/

                // LocalInstrument
            }
            el("PmtInf") {
                el("PmtInfId", "NOTPROVIDED")
                el("PmtMtd", "TRF")
                el("BtchBookg", "false")
                el("NbOfTxs", msg.txs.size.toString())
                el("CtrlSum", totalAmount)
                if (dialect == Dialect.gls) {
                    el("PmtTpInf") {
                        el("SvcLvl/Cd", "SEPA")
                        if (instant) {
                            el("LclInstrm/Cd", "INST")
                        }
                    }
                }
                el("ReqdExctnDt/Dt", DateTimeFormatter.ISO_DATE.format(zonedTimestamp))
                el("Dbtr/Nm", msg.debtor.name)
                el("DbtrAcct/Id/IBAN", msg.debtor.iban.toString())
                el("DbtrAgt/FinInstnId") {
                    if (msg.debtor.bic != null) {
                        el("BICFI", msg.debtor.bic)
                    } else {
                        el("Othr/Id", "NOTPROVIDED")
                    }
                }
                el("ChrgBr", "SLEV")
                for (tx in msg.txs) {
                    el("CdtTrfTxInf") {
                        el("PmtId") {
                            el("InstrId", tx.endToEndId)
                            // Used to uniquely identify transactions in other files
                            el("EndToEndId", tx.endToEndId)
                        }
                        el("Amt/InstdAmt") {
                            attr("Ccy", tx.amount.currency)
                            text(getAmountNoCurrency(tx.amount))
                        }
                        if (tx.creditor.bic != null) el("CdtrAgt/FinInstnId/BICFI", tx.creditor.bic)
                        el("Cdtr") {
                            el("Nm", tx.creditor.name)
                            // Addr might become a requirement in the future
                            /*el("PstlAdr") {
                                el("TwnNm", "Bochum")
                                el("Ctry", "DE")
                            }*/
                        }
                        el("CdtrAcct/Id/IBAN", tx.creditor.iban.toString())
                        el("RmtInf/Ustrd", tx.subject)
                    }
                }
            }
        }
    }
}