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

import io.ktor.http.HttpHeaders
import io.ktor.serialization.kotlinx.*
import io.ktor.server.application.*
import io.ktor.server.routing.*
import io.ktor.server.testing.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement
import tech.libeufin.ebics.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs

class WsTest {
    // WSS params example from the spec
    val PARAMS_EXAMPLE = """
        {
            "URL": "https://bankmitwebsocket.de",
            "TOKEN": "550e8400-e29b-11d4-a716-446655440000",
            "OTT": "N",
            "VALIDITY": "2019-03-21T10:35:22Z",
            "PARTNERID": "K1234567",
            "USERID": "USER4711"
        }
        """
    // Authorization header example from the spec
    val AUTH_EXAMPLE = "Basic SzEyMzQ1NjdfVVNFUjQ3MTE6NTUwZTg0MDAtZTI5Yi0xMWQ0LWE3MTYtNDQ2NjU1NDQwMDAw"
    // Notifications examples from the spec
    val NOTIFICATION_EXAMPLES = sequenceOf(
        """
        {
            "MCLASS": [
                {
                    "NAME": "EBICS-HAA",
                    "VERS": "1.0",
                    "TIMESTAMP": "2019-05-13T12:21:50Z"
                }
            ],
            "PARTNERID": "K1234567",
            "USERID": "USER471",
            "BTF": [
                {
                    "SERVICE": "REP",
                    "SCOPE": "DE",
                    "CONTTYPE": "ZIP",
                    "MSGNAME": "camt.054"
                }
            ],
            "ORDERTYPE": [
                "C5N"
            ]
        }
        """, """
        {
            "MCLASS": [
                {
                    "NAME": "EBICS-HAA",
                    "VERS": "1.0",
                    "TIMESTAMP": "2019-05-13T12:21:53Z"
                }
            ],
            "PARTNERID": "K1234567",
            "USERID": "USER471",
            "BTF": [
                {
                    "SERVICE": "REP",
                    "SCOPE": "DE",
                    "CONTTYPE": "ZIP",
                    "MSGNAME": "camt.052"
                },
                {
                    "SERVICE": "REP",
                    "SCOPE": "DE",
                    "OPTION": "SCI",
                    "CONTTYPE": "ZIP",
                    "MSGNAME": "pain.002"
                }
            ],
            "ORDERTYPE": [
                "C52",
                "CIZ"
            ]
        }
        """, """
        {
            "MCLASS": [
                {
                    "NAME": "INFO",
                    "VERS": "1.0",
                    "TIMESTAMP": "2019-03-25T12:25:34Z"
                }
            ],
            "INFO": [
                {
                    "LANG": "EN",
                    "FREE": " The EBICS-Service is limited on 30.03.2019 from 10:00 a.m. - 11:00a.m. due to maintenance work "
                }
            ]
        }
        """
    )

    /** Test JSON serialization roudtrip */
    inline fun <reified B> roundtrip(raw: String): B {
        val json: JsonObject = Json.decodeFromString(raw)
        val decoded: B = Json.decodeFromJsonElement(json)
        val encoded = Json.encodeToJsonElement(decoded)
        assertEquals(json, encoded)
        return decoded
    }

    /** Test our serialization implementation works with spec examples */
    @Test
    fun serialization() {
        roundtrip<WssParams>(PARAMS_EXAMPLE)
        for (raw in NOTIFICATION_EXAMPLES) {
            roundtrip<WssNotification>(raw)
        }
    }

    /** Test our implementation works with spec examples */
    @Test
    fun wss() {
        val params: WssParams = Json.decodeFromString(PARAMS_EXAMPLE)

        testApplication {
            externalServices {
                hosts(params.URL.replace("https://", "wss://")) {
                    install(WebSockets) {
                        contentConverter = KotlinxWebsocketSerializationConverter(Json)
                    }
                    routing {
                        webSocket("/") {
                            assertEquals(AUTH_EXAMPLE, call.request.headers[HttpHeaders.Authorization])
                            // Send all examples
                            for (example in NOTIFICATION_EXAMPLES) {
                                send(example)
                            }
                            close(CloseReason(CloseReason.Codes.NORMAL, "Test done"))
                        }
                    }
                }
            }
            var count = 0
            try {
                params.connect(client) { msg ->
                    count++
                    // Check message number and type
                    assert(count <= 3)
                    if (count == 3) {
                        assertIs<WssGeneralInfo>(msg)
                    } else {
                        assertIs<WssNewData>(msg)
                    }
                }
            } catch (e: ClosedReceiveChannelException) {
                // Expected
            }
            // Check receive all messages
            assertEquals(3, count)
        }
    }
}