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

import io.ktor.client.*
import tech.libeufin.common.*
import tech.libeufin.ebics.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory

data class TxCheckResult(
    var concurrentFetchAndFetch: Boolean = false,
    var concurrentFetchAndSubmit: Boolean = false,
    var concurrentSubmitAndSubmit: Boolean = false,
    var idempotentClose: Boolean = false
)

/**
 * Test EBICS implementation's transactions semantic:
 * - Can two fetch transactions run concurrently ?
 * - Can a fetch & submit transactions run concurrently ?
 * - Can two submit transactions run concurrently ?
 * - Is closing a submit transaction idempotent
 */
suspend fun txCheck(
    client: HttpClient,
    cfg: EbicsHostConfig,
    clientKeys: ClientPrivateKeysFile,
    bankKeys: BankPublicKeysFile,
    fetchOrder: EbicsOrder,
    submitOrder: EbicsOrder
): TxCheckResult {
    val result = TxCheckResult()
    val fetch = EbicsBTS(cfg, bankKeys, clientKeys, fetchOrder)
    val submit = EbicsBTS(cfg, bankKeys, clientKeys, submitOrder)
    val ebicsLogger = EbicsLogger(null).tx("test").step("step")

    suspend fun EbicsBTS.close(id: String, phase: String, ebicsLogger: StepLogger) {
        val xml = downloadReceipt(id, false)
        postBTS(client, xml, phase, ebicsLogger)
    }

    val firstTxId = fetch.postBTS(client, fetch.downloadInitialization(null, null), "Init first fetch", ebicsLogger)
        .transactionID!!
    try {
        try {
            val id = fetch.postBTS(client, fetch.downloadInitialization(null, null), "Init second fetch", ebicsLogger).transactionID!!
            result.concurrentFetchAndFetch = true
            fetch.close(id, "Init second fetch", ebicsLogger)
        } catch (e: EbicsError.Code) {}
        
        var paylod = prepareUploadPayload(cfg, clientKeys, bankKeys, ByteArray(2000000).rand())
        try {
            val submitId = submit.postBTS(client, submit.uploadInitialization(paylod), "Init first submit", ebicsLogger). transactionID!!
            result.concurrentFetchAndSubmit = true
            submit.postBTS(client, submit.uploadTransfer(submitId, paylod, 1), "Submit first upload", ebicsLogger)
            try {
                submit.postBTS(client, submit.uploadInitialization(paylod), "Init second submit", ebicsLogger)
                result.concurrentSubmitAndSubmit = true
            } catch (e: EbicsError.Code) {}
        } catch (e: EbicsError.Code) {}
    } finally {
        fetch.close(firstTxId, "Close first fetch", ebicsLogger)
    }

    try {
        fetch.close(firstTxId, "Close first fetch a second time", ebicsLogger)
        result.idempotentClose = true
    } catch (e: Exception) {
        logger.debug { e.fmt() }
    }

    return result
}