package com.github.mondei1.offpass

import android.content.Context
import android.util.Base64
import android.util.Log
import com.github.mondei1.offpass.entities.Compression
import com.github.mondei1.offpass.entities.CompressionHelper
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import java.lang.Error

class QRSchema {

    private var db: CompressionHelper? = null

    var raw: String = ""
    var decrypted_raw: String = ""

    // Parsed content
    var session_key: String = ""
    lateinit var title: String
    lateinit var username: String
    lateinit var password: String
    lateinit var email: String
    lateinit var website_url: String
    var custom: HashMap<String, String> = HashMap()             // All defined custom/optional fields
    var question_awnser: HashMap<String, String> = HashMap()    // Used for security questions

    constructor(context: Context) {
        this.db = CompressionHelper(context, null)
    }

    /**
     * This function takes a key `key` and tries to resolve it.
     *
     * @return The decrypted value associated with the key.
     */
    private suspend fun resolve(key: String, context: Context): String {
        val to_resolve = key.replace("$", "")
        Log.i("QR-Code schema", "Try to resolve $to_resolve")
        val database = db

        return GlobalScope.async<String> {
            var cursor = database!!.get(session_key, to_resolve)
            if (cursor != null) {
                cursor.toString()
            } else {
                ""
            }
        }.await()
    }

    /**
     * This function will take the already set raw content and tries to parse it.
     *
     * @return It will return `true` if the operation was successful and `false` if otherwise.
     * Parsed objects can be read from this class instance.
     */
    fun parse(context: Context): Boolean {
        if (decrypted_raw == "") {
            throw Error("Tried to parse QR-Code schema but raw content must be first decrypted! " +
                    "Set raw content first and then use decrypt()")
        }

        // First will be to split the string into it's parts
        val fields: MutableList<String> = this.decrypted_raw.split("|").toMutableList()

        for (i in 0 until fields.size) {
            // First four items have to exist and are title, username, password, URL in this order.
            if (i == 0) {
                // Check if there is a session key
                if (fields[0].startsWith("%")) {
                    fields[0] = fields[0].replace("%", "")
                    session_key = fields[0].substring(0, 10) // Get first 10 chars, which are the
                                                            // session key
                    this.title = fields[0].substring(10, fields[0].length)
                } else {
                    this.title = fields[0]
                }

                this.username = fields[1]
                this.password = fields[2]
                this.email = fields[3]
                this.website_url = fields[4]

                // Look if website url is compressed
                if (this.website_url.startsWith("$")) {
                    val that_class = this
                    runBlocking {
                        that_class.website_url = resolve(that_class.website_url, context)
                    }
                }
            }

            // Here are optional/custom fields
            if (i > 4) {
                if (fields[i].startsWith("(") && fields[i] != "") {
                    var closingBracket = fields[i].indexOf(")")
                    var key = fields[i].substring(1, closingBracket)
                    var value = fields[i].substring(closingBracket+1, fields[i].length)

                    // We got a security question/awnser
                    if (key == "") {
                        val qa = value.split("%")
                        question_awnser.put(qa[0], qa[1]);
                    } else {
                        // Check if key and or value are compressed
                        if (key.startsWith("$")) {
                            runBlocking {
                                key = resolve(key, context)
                            }
                        }
                        if (value.startsWith("$")) {
                            runBlocking {
                                value = resolve(value, context)
                            }
                        }

                        custom.put(key, value)
                    }
                } else {
                    throw Error("Custom/optional field should start with an open bracket: "
                            + fields[i].toString())
                }
            }
        }
        Log.i("QR-Code schema", "Found: $session_key, $title, $username, $password, " +
                "$email, $website_url. ${custom.toString()} and ${question_awnser.toString()}")

        return true
    }

    /**
     * This function will take an raw string input and will decrypt it.
     *
     * @return It returns the decrypted raw value.
     */
    fun decrypt(raw: String, passphrase: String): String {
        val parts = raw.split(":")
        val iv = parts[1]
        val salt = parts[2]
        val encrypted_content = parts[3]
        Log.i("Decrypt schema", "Give data: $encrypted_content")

        this.decrypted_raw = CryptoOperations().decrypt(
            encrypted_content, passphrase,
            iv.toByteArray(), salt.toByteArray())

        return this.decrypted_raw
    }

    /**
     * This function will take all defined variables and converts them into the QR-Code schema.
     *
     * @param compress_values Takes an string array where a entry can be `website_url` in case of an URL or
     * the key of an optional field. For a security question take the question as key.
     */
    fun build(compress_values: Array<String>, passphrase: String): String {
        /* First phase is to construct the encrypted data. */
        val session_key = CryptoOperations().nextString(10) // used for
        var used_compression: Boolean = false

        var url_copy = website_url
        if (compress_values.contains("website_url")) {
            used_compression = true
            val email_key = CryptoOperations().nextString(4)
            this.db?.add(session_key, Compression(email_key, website_url, null, null))
            url_copy = "\$$email_key"
        }
        var encrypted_content: String =
            "${this.title}|${this.username}|${this.password}|${this.email}|${url_copy}|"

        // Loop thought optional/custom fields
        for (i in this.custom) {
            if (compress_values.contains(i.key)) {
                used_compression = true
                var key_key = CryptoOperations().nextString(4)
                var key_value = CryptoOperations().nextString(4)

                this.db?.add(session_key,
                    Compression(key_key, i.key, null, null))
                this.db?.add(session_key,
                    Compression(key_value, i.value, null, null))

                encrypted_content += "(\$$key_key)\$$key_value|"
            } else {
                encrypted_content += "(${i.key})${i.value}|"
            }
        }

        // Loop thought security questions
        for (i in this.question_awnser) {
            used_compression = true
            if (compress_values.contains(i.key)) {
                var key_key = CryptoOperations().nextString(4)
                var key_value = CryptoOperations().nextString(4)

                this.db?.add(session_key,
                    Compression(key_key, i.key, null, null))
                this.db?.add(session_key,
                    Compression(key_value, i.value, null, null))

                encrypted_content += "()\$$key_key%\$$key_value|"
            } else {
                encrypted_content += "()${i.key}%${i.value}|"
            }
        }
        encrypted_content = encrypted_content.dropLast(1)
        if (used_compression) encrypted_content = "%$session_key%$encrypted_content"

        Log.i("QR-Code Builder", "Constructed encrypted content: $encrypted_content")

        // Now, let's encrypt that
        val enc = runBlocking {
            CryptoOperations().encrypt(passphrase, encrypted_content)
        }

        // TODO: Make schema version dynamic (currently hardcoded)
        var final = "op1:" +
                "${String(enc.iv)}:" +
                "${String(enc.salt)}:" +
                enc.result
                    .replace("\n", "")

        Log.i("QR-Code Builder", "Returning final result: $final")

        this.raw = final

        return final

    }

}