/*
 * 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.nexus.*
import tech.libeufin.ebics.XmlDestructor
import java.io.InputStream

private fun fmtMsg(code: String?, description: String?, reasons: List<Reason>) = buildString {
    if (code != null) {
        append(code)
        append(" ")
        if (description != null) {
            append("'")
            append(description)
            append("'")
        }
        if (reasons.isNotEmpty()) {
            append(":")
        }
    }
    for (reason in reasons) {
        append(" ")
        if (reason.code != null) {
            append(reason.code.isoCode)
            append(" '")
            append(reason.code.description)
            append("'")
        }
        if (reason.information.isNotEmpty()) {
            if (reason.code != null) append(" ")
            append("'")
            append(reason.information)
            append("'")
        }
    }
}

data class MsgStatus(
    val id: String,
    val code: ExternalPaymentGroupStatusCode?,
    val reasons: List<Reason>,
    val payments: List<PmtStatus>
) {
    fun msg() = fmtMsg(code?.isoCode, code?.description, reasons)
    override fun toString() = buildString {
        append(id)
        val msg = msg()
        if (msg.isNotEmpty()) {
            append(" ")
            append(msg)
        }
        for (pmt in payments) {
            append("\n>")
            append(pmt.id)
            val msg = pmt.msg()
            if (msg.isNotEmpty()) {
                append(" ")
                append(msg)
            }

            for (tx in pmt.transactions) {
                append("\n>>")
                if (tx.id != tx.endToEndId) {
                    append(tx.id)
                    append(" ")
                }
                append(tx.endToEndId)
                val msg = tx.msg()
                if (msg.isNotEmpty()) {
                    append(" ")
                    append(msg)
                }
            }
        }
    }
}

data class PmtStatus(
    val id: String,
    val code: ExternalPaymentGroupStatusCode?,
    val reasons: List<Reason>,
    val transactions: List<TxStatus>
) {
    fun msg() = fmtMsg(code?.isoCode, code?.description, reasons)
}

data class TxStatus(
    val id: String,
    val endToEndId: String,
    val code: ExternalPaymentTransactionStatusCode,
    val reasons: List<Reason>
) {
    fun msg() = fmtMsg(code.isoCode, code.description, reasons)
}

data class Reason (
    val code: ExternalStatusReasonCode?,
    val information: String
)

/** Parse pain.002 XML file */
fun parseCustomerPaymentStatusReport(xml: InputStream): MsgStatus {
    fun XmlDestructor.reasons(): List<Reason> {
        return map("StsRsnInf") {
            val code = opt("Rsn")?.one("Cd")?.enum<ExternalStatusReasonCode>()
            val info = map("AddtlInf") { text() }.joinToString("")
            Reason(code, info)
        }
    }
    
    return XmlDestructor.parse(xml, "Document") {
        one("CstmrPmtStsRpt") {
            val (id, code, reasons) = one("OrgnlGrpInfAndSts") {
                val id = one("OrgnlMsgId").text()
                val code = opt("GrpSts")?.enum<ExternalPaymentGroupStatusCode>()
                val reasons = reasons()
                Triple(id, code, reasons)
            }
            val payments = map("OrgnlPmtInfAndSts") {
                val id = one("OrgnlPmtInfId").text()
                val code = opt("PmtInfSts")?.enum<ExternalPaymentGroupStatusCode>()
                val reasons = reasons()
                val transactions = map("TxInfAndSts") {
                    val id = one("OrgnlInstrId").text()
                    val endToEndId = one("OrgnlEndToEndId").text()
                    val code = one("TxSts").enum<ExternalPaymentTransactionStatusCode>()
                    val reasons = reasons()
                    TxStatus(id, endToEndId, code, reasons)
                }
                PmtStatus(id, code, reasons, transactions) 
            }
            MsgStatus(id, code, reasons, payments)
        }
    }
}