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

@JvmInline
value class IBAN private constructor(val value: String) {
    override fun toString(): String = value

    companion object {
        private val SEPARATOR = Regex("[\\ \\-]")

        fun checksum(iban: String): Int = 
            (iban.substring(4 until iban.length).asSequence() + iban.substring(0 until 4).asSequence())
                .fold(0) { acc, char ->
                    if (char.isDigit()) {
                        (acc * 10 + char.code - '0'.code) % 97
                    } else {
                        (acc * 100 + char.code - 'A'.code + 10) % 97
                    }
                }
            

        fun parse(raw: String): IBAN {
            val iban: String = raw.uppercase().replace(SEPARATOR, "")
            if (iban.length < 5) {
                throw CommonError.Payto("malformed IBAN, string is too small only ${iban.length} char")
            }
            val countryCode = iban.substring(0 until 2)
            for (c in countryCode) {
                if (!c.isLetter()) throw CommonError.Payto("malformed IBAN, malformed country code")
            }
            for (c in iban.substring(2 until 4)) {
                if (!c.isDigit()) throw CommonError.Payto("malformed IBAN, malformed check digit")
            }
            val country = try {
                Country.valueOf(countryCode)
            } catch (e: IllegalArgumentException) {
                throw CommonError.Payto("malformed IBAN, unknown country $countryCode")
            }
            if (country == Country.DE && iban.length != 22) {
                // This is allowed for retrocompatibility with libeufin-bank malformed DE IBAN
            } else if (!country.bbanRegex.matches(iban.substring(4))) {
                throw CommonError.Payto("malformed IBAN, invalid char")
            }
            val checksum = checksum(iban)
            if (checksum != 1) throw CommonError.Payto("malformed IBAN, modulo is $checksum expected 1")
            return IBAN(iban)
        }

        fun rand(country: Country): IBAN {
            val bban = buildString {
                for ((repetition, c) in country.rules) {
                    val alphabet = when (c) {
                        IbanC.n -> "0123456789"
                        IbanC.a -> "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                        IbanC.c -> "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
                    }
                    repeat(repetition) {
                        append(alphabet.random())
                    }
                }
            }
            val checkDigits = 98 - checksum("${country.name}00$bban");
            return if (checkDigits < 10) {
                IBAN("${country.name}0$checkDigits$bban")
            } else {
                IBAN("${country.name}$checkDigits$bban")
            }
        }
    }
}