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

import org.w3c.dom.Document
import java.io.InputStream

data class VersionNumber(val number: Float, val schema: String) {
    override fun toString(): String = "$number:$schema"
}

data class HKD(
    val partner: PartnerInfo,
    val users: List<UserInfo>
)
data class PartnerInfo(
    val name: String?,
    val accounts: List<AccountInfo>,
    val orders: List<OrderInfo>
)
data class OrderInfo(
    val order: EbicsOrder,
    val description: String,
)
data class AccountInfo(
    val currency: String,
    val iban: String,
    val bic: String
)
data class UserInfo(
    val id: String,
    val status: UserStatus,
    val permissions: List<EbicsOrder>,
)

data class HAA(
    val orders: List<EbicsOrder>
)

enum class UserStatus(val description: String) {
    Ready("Subscriber is permitted access"),
    New("Subscriber is established, pending access permission"),
    INI("Subscriber has sent INI file, but no HIA file yet"),
    HIA("Subscriber has sent HIA order, but no INI file yet"),
    Initialised("Subscriber has sent both HIA order and INI file"),
    SuspendedFailedAttempts("Suspended after several failed attempts, new initialisation via INI and HIA possible"),
    SuspendedSPR("Suspended after SPR order, new initialisation via INI and HIA possible"),
    SuspendedBank("Suspended by bank, new initialisation via INI and HIA is not possible, suspension can only be revoked by the bank"),
}

object EbicsAdministrative {
    fun HEV(cfg: EbicsHostConfig): ByteArray {
        return XmlBuilder.toBytes("ebicsHEVRequest") {
            attr("xmlns", "http://www.ebics.org/H000")
            el("HostID", cfg.hostId)
        }
    }

    fun parseHEV(doc: Document): EbicsResponse<List<VersionNumber>> {
        return XmlDestructor.parse(doc, "ebicsHEVResponse") {
            val technicalCode = one("SystemReturnCode") {
                EbicsReturnCode.lookup(one("ReturnCode").text())
            }
            val versions = map("VersionNumber") {
                VersionNumber(text().toFloat(), attr("ProtocolVersion"))
            }
            EbicsResponse(
                technicalCode = technicalCode, 
                bankCode = EbicsReturnCode.EBICS_OK,
                content = versions
            )
        }
    }

    private fun XmlDestructor.ebicsOrder(type: String): EbicsOrder = 
        EbicsOrder.V3(
            type = type,
            service = opt("ServiceName")?.text(),
            scope = opt("Scope")?.text(),
            option = opt("ServiceOption")?.text(),
            container = opt("Container")?.attr("containerType"),
            message = opt("MsgName")?.text(),
            version = opt("MsgName")?.optAttr("version"),
        )

    fun parseHKD(stream: InputStream): HKD { 
        fun XmlDestructor.order(): EbicsOrder {
            val type = one("AdminOrderType").text()
            return opt("Service") {
                ebicsOrder(type)
            } ?: EbicsOrder.V3(type)
        }
        return XmlDestructor.parse(stream, "HKDResponseOrderData") {
            val partnerInfo = one("PartnerInfo") {
                val name = one("AddressInfo").opt("Name")?.text()
                val accounts = map("AccountInfo") {
                    var currency = attr("Currency")
                    lateinit var iban: String
                    lateinit var bic: String
                    each("AccountNumber") {
                        if (attr("international") == "true") {
                            iban = text()
                        }
                    }
                    each("BankCode") {
                        if (attr("international") == "true") {
                            bic = text()
                        }
                    }
                    AccountInfo(currency, iban, bic)
                }
                val orders = map("OrderInfo") { 
                    OrderInfo(
                        order = order(),
                        description = one("Description").text()
                    )
                }
                PartnerInfo(name, accounts, orders)
            }
            val usersInfo = map("UserInfo") {
                val (id, status) = one("UserID") {
                    val id = text()
                    val status = when (val status = attr("Status")) {
                        "1" -> UserStatus.Ready
                        "2" -> UserStatus.New
                        "3" -> UserStatus.INI
                        "4" -> UserStatus.HIA
                        "5" -> UserStatus.Initialised
                        "6" -> UserStatus.SuspendedFailedAttempts
                        // 7 is not applicable per spec
                        "8" -> UserStatus.SuspendedSPR
                        "9" -> UserStatus.SuspendedBank
                        else -> throw Exception("Unknown user statte $status")
                    }
                    Pair(id, status)
                }
                val permissions = map("Permission") { order() }
                UserInfo(id, status, permissions)
            }
            HKD(partnerInfo, usersInfo)
        }
    }

    fun parseHAA(stream: InputStream): HAA {
        return XmlDestructor.parse(stream, "HAAResponseOrderData") {
            val orders = map("Service") {
                ebicsOrder("BTD")
            }
            HAA(orders)
        }
    }
}
