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

import org.bouncycastle.math.ec.rfc8032.Ed25519

private val PART_PATTERN = Regex("[:a-z0-9A-Z]*")
private val BASE32_32B_PATTERN = Regex("(KYC:)?([a-z0-9A-Z]{52})")

data class TalerIncomingMetadata(val type: TalerIncomingType, val key: EddsaPublicKey)

/** 
 * Extract the reserve public key from an incoming Taler transaction subject
 * 
 * We first try to find a whole key. If none are found we try to reconstruct
 * one from parts.
 **/
fun parseIncomingTxMetadata(subject: String): TalerIncomingMetadata {
    /** Check one or no reserve public key is found  **/
    fun check(matches: Sequence<MatchResult>): TalerIncomingMetadata? {
        val iterator = matches.iterator()

        // Check none
        if (!iterator.hasNext()) {
            return null
        }

        val (prefix, base32) = iterator.next().destructured

        // Check many
        if (iterator.hasNext()) {
            throw Exception("Found multiple reserve public key")
        }

        // Check key validity
        val key = EddsaPublicKey(base32)
        if (!Ed25519.validatePublicKeyFull(key.raw, 0)) {
            return null
        }

        // Check key type
        val type = if (prefix == "KYC:") TalerIncomingType.kyc else TalerIncomingType.reserve
        return TalerIncomingMetadata(type, key)
    }

    // Wire transfer subjects are generally small in size, and not
    // being able to find the encoded reserve public key poses a huge
    // usability problem. So we're ready to work hard to find it.

    // Bank interfaces doesn't always allow a complete encoded key to 
    // be entered on a single line, so users have to split them, which
    // may lead them to add an erroneous space or + or - separator.
    // If we can't find a key, we try to eliminate these common errors 
    // before trying again.

    // Since any sequence of 52 upper and lowercase characters can be a 
    // valid encoded key, deleting spaces and separators can create false 
    // positives, so we always start by searching for a valid whole key
    // then we try to reconstruct a key from valid parts.

    // Whole key match
    val key = check(BASE32_32B_PATTERN.findAll(subject))
    if (key != null) return key

    // Key reconstruction from parts
    val parts = PART_PATTERN.findAll(subject).map { it.value }.toList()
    for (windowSize in 2..parts.size) {
        val matches = parts.windowed(windowSize).asSequence().map { window ->
            val joined = window.joinToString("")
            BASE32_32B_PATTERN.matchEntire(joined)
        }.filterNotNull()
        val key = check(matches)
        if (key != null) return key
    }

    // No key where found
    throw Exception("Missing reserve public key")
}

/** Extract the reserve public key from an incoming Taler transaction subject */
fun parseOutgoingTxMetadata(subject: String): Pair<ShortHashCode, ExchangeUrl>  {
    val (wtid, baseUrl) = subject.splitOnce(" ") ?: throw Exception("Malformed outgoing subject")
    return Pair(EddsaPublicKey(wtid), ExchangeUrl(baseUrl))
}