import kotlinx.serialization.json.Json
import nl.aet.cid.client.sdk.*
import nl.aet.cid.client.sdk.desktop.Configuration
import nl.aet.cid.client.sdk.desktop.InstanceManager
import nl.aet.cid.client.sdk.types.*
import java.io.File
import java.io.IOException
import java.util.*
import kotlin.coroutines.suspendCoroutine
import nl.whiteboxsystems.tokens.WBXAuthToken
import java.lang.System
import kotlin.system.exitProcess

fun setupManager() : IInstanceManager {
    val logPath = "${File("").absolutePath}/logs/java_sdk.log"
    val logTimingPath = "${File("").absolutePath}/logs/java_sdk_timing.log"
    val sdkLoggerConf = LoggerConfiguration(LogLevel.Verbose, logPath, LogTarget.File)
    val timingLoggerConf = LoggerConfiguration(LogLevel.Verbose, logTimingPath, LogTarget.File)
    InstanceManager.initialize(Configuration("", sdkLoggerConf, timingLoggerConf))
    return InstanceManager.instance()
}

class Callback(success: (args: TransactionFinishedEventArgs) -> Unit, failure: (args: TransactionCancelledEventArgs) -> Unit) {
    val success  = success
    val failure = failure
}

class App() {
    private val manager = setupManager()
    private var sessionId : UUID? = null
    private val transactions = mutableMapOf<UUID, Callback>()
    private var elemCallback : (() -> Unit)? = null
    private var elemFailedCallback : (() -> Unit)? = null
    private var sessionOpened : (() -> Unit)? = null
    private var sessionOpenedFailed : (() -> Unit)? = null

    private var secureElement : SecureElement? = null

    init {
        manager.secureElementStore.addHandler(
            ISecureElementStore.SECURE_ELEMENT_RETRIEVED,
            SecureElementRetrievedEventHandler { _, eventArgs ->
                secureElement = eventArgs.secureElement
                elemCallback?.invoke()
            })

        manager.secureElementStore.addHandler(
                  ISecureElementStore.SECURE_ELEMENT_CANCELLED,
        SecureElementCancelledEventHandler { _, _ ->
            elemFailedCallback?.invoke()
        })

        manager.sessionManager.addHandler(ISessionManager.SESSION_OPENED,
            SessionOpenedEventHandler { _, _ ->
               sessionOpened?.invoke()
            })

        manager.sessionManager.addHandler(ISessionManager.SESSION_OPEN_CANCELLED,
            SessionOpenCancelledEventHandler { _, _ ->
                sessionOpenedFailed?.invoke()
            })

        manager.transactionManager.addHandler(ITransactionManager.TRANSACTION_FINISHED,
            TransactionFinishedEventHandler { _, eventArgs ->
                val cb = transactions[eventArgs.transactionId]
                cb?.success?.invoke(eventArgs)
            })
        manager.transactionManager.addHandler(ITransactionManager.TRANSACTION_CANCELLED,
            TransactionCancelledEventHandler { _, eventArgs ->
                val cb = transactions[eventArgs.transactionId]
                cb?.failure?.invoke(eventArgs)
            })
    }

    private suspend fun retSecElem() {
        manager.secureElementStore.retrieve()
        return suspendCoroutine { cont ->
            this.elemCallback ={
                cont.resumeWith(Result.success(Unit))
            }

            this.elemFailedCallback ={
                cont.resumeWith(Result.failure(IOException("Could not retrieve Secure Element")))
            }
        }
    }

    private suspend fun openSession(pin: String) : UUID {
        val sessionId: UUID = manager.sessionManager.openAttached(pin.toCharArray())
        println("-- Opening session with sessionId: $sessionId")
        manager.secureElementStore.retrieve()

        return suspendCoroutine { cont ->
            this.sessionOpened ={
                cont.resumeWith(Result.success(sessionId))
            }

            this.sessionOpenedFailed ={
                cont.resumeWith(Result.failure(IOException("Could not open session")))
            }
        }
    }

    suspend fun init(pin: String) {
        retSecElem()
        val sessionId = openSession(pin)
        this.sessionId = sessionId
        println("-- Session Opened: $sessionId")
    }

    suspend fun sign(nonce: String, bsn: String, url: String, key_fp: String?) : WBXAuthToken {
        println("---- Signing data ----")

        val certData = secureElement?.certificates?.filter { x -> x.keyUsage == KeyUsage.ClientAuthentication }?.get(0)
        val token = WBXAuthToken(nonce, bsn, url, certData, key_fp)
        val hashBag = token.getHashBag()

        val hashTransactionId = manager.transactionManager.sign(sessionId, hashBag)
        return suspendCoroutine { cont ->
            val callback = Callback({
                val result = it.signedDataBag.content.values.iterator().next().data
                token.convertAndSetSignature(result)
                cont.resumeWith(Result.success(token))
            }, {
                cont.resumeWith(Result.failure(IOException("Could not open session")))
            })
            this.transactions[hashTransactionId] = callback
        }
    }

    fun shutdown() {
        manager.dispose()
    }

}

fun renderHTML(wbxAuthToken : WBXAuthToken) : String {
    val startURL = wbxAuthToken.endpoint()


    val data = """<!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    <h1>Topicus HAP mock</h1>
    <iframe 
        style="width: 100%" 
        srcdoc="<html>
            <head></head>
            <body>
                <form method='POST' action='${startURL}'>
                    <input type='hidden' name='x-access-token' value='${wbxAuthToken.jws()}'><input type='hidden' name='url' value='${wbxAuthToken.url()}'>
                </form>
                <script>document.querySelector('form').submit()</script>
            </body>
            </html>
        " frameborder="0">
    </iframe>
    <script>    
    var iframe = document.querySelector('iframe')
    window.addEventListener('message', function (e) {
        iframe.style.height = e.data.height + 'px'
    });
    </script>
    </body>
    </html>"""

    return Base64.getEncoder().encodeToString(data.toByteArray())
}

suspend fun main(args: Array<String>) {
    val instance = App()
    val scanner = Scanner(System.`in`)




    println("---- Setup ----")
    println("-- Make sure the card is correctly inserted in the terminal, enter pin and press enter.")
    val pincode = scanner.nextLine()
    instance.init(pincode)

    println("-- Use dvpa-r template? y/n.")
    val useDvpar = scanner.nextLine() == "y"

    var nonce = ""
    var url = ""
    var key_fp : String? = null

    if (useDvpar) {
        val fileContent = File("./dvpa-r-data/token_template.json").readText()
        val tokenTemplate = Json.decodeFromString(DVPARTokenTemplate.serializer(), fileContent)
        nonce = tokenTemplate.data?.claims?.challenge.toString()
        url = tokenTemplate.data?.claims?.url.toString()
        key_fp = tokenTemplate.data?.claims?.system?.keyFp.toString()
    } else {
        println("-- Enter challenge")
        nonce = scanner.nextLine()

        println("-- Enter url")
        url = scanner.nextLine()
    }

    println("-- Enter patientBsn")
    var bsn = scanner.nextLine()

    val wbxToken = instance.sign(nonce, bsn, url, key_fp)

    if (useDvpar) {
        File("./dvpa-r-data/token.json").writeText(wbxToken.jws())
        println("Written to ./dvpa-r-data/token.json")
    } else {
        val data = renderHTML(wbxToken)
        Runtime.getRuntime().exec("C:\\Program\\ Files\\Google\\Chrome\\Application\\chrome.exe data:text/html;base64,${data}")
    }

    instance.shutdown()
    exitProcess(0)
}
