package com.github.mondei1.offpass

import android.os.HandlerThread
import android.os.Looper
import android.util.Base64
import android.util.Log
import java.nio.ByteBuffer
import java.security.SecureRandom
import java.security.spec.KeySpec
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec

class EncryptionResult(
    val result: String = "",
    val salt: ByteArray = ByteArray(1),
    val iv: ByteArray = ByteArray(1)
) {
}

class CryptoOperations {
    private val random: SecureRandom = SecureRandom()
    private val possibleChars: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    private lateinit var chars: CharArray

    fun getRemainingTOTPTime(): Int {
        /*
         * If the time (in seconds) is 13, then this function should return 17.
         * If the time ("") is 46, then this function should return 14
         */
        val format: DateFormat = SimpleDateFormat("ss")
        val time_seconds = format.format(Calendar.getInstance().time).toInt()

        if (time_seconds <= 30) return 30 - time_seconds
        else return 60 - time_seconds
    }

    /**
     * Hashes a any input with
     *
     * @return a secure authentication token to be stored for later authentication
     */
    fun hash(password: CharArray, salt: ByteArray): String {
        val spec: KeySpec = PBEKeySpec(password, salt, 8000, 256)
        val factory: SecretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
        val hash = factory.generateSecret(spec).encoded

        Log.i("Crypto", "Hashed ${password.toString()} to ${Base64.encodeToString(hash, Base64.DEFAULT)}")
        return Base64.encodeToString(hash, Base64.DEFAULT)
    }

    /**
     * This function encrypts a provided string `plain` with a provided key `key`.
     * Encryption function is AES-256-CBC
     *
     * @return Encrypted plain in Base64 encoded (without wrapping)
     */
    fun encrypt(key: String, plain: String): EncryptionResult {
        val salt = nextString(10).toByteArray()
        val iv = nextString(16).toByteArray()
        val key_bytes: ByteArray = hash(key.toCharArray(), salt).toByteArray(Charsets.UTF_8)
        val aes_key: ByteArray = ByteArray(32)

        // Now make the key bytes fit into 256 bits
        if (key_bytes.size < aes_key.size) {
            throw Error("Tried to cut down hash bytes to 32 but there are less then that.")
        }
        for (i in aes_key.indices) {
            aes_key.set(i, key_bytes[i])
        }

        val key: SecretKey = SecretKeySpec(aes_key, "AES")
        val cipher: Cipher = Cipher.getInstance("AES/CBC/ISO10126Padding")

        // Performing actual crypto operation
        cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
        Log.i("Crypto", "Encrypt using ${String(aes_key)} '$plain' ${String(iv)}")
        val ciphered = cipher.doFinal(plain.toByteArray(Charsets.UTF_8))

        Log.d("Crypto", "Encrypted $plain => ${Base64.encodeToString(ciphered, Base64.NO_WRAP)}")
        return EncryptionResult(Base64.encodeToString(ciphered, Base64.NO_WRAP), salt, iv)
    }

    /**
     * This function decrypts a provided string `encrypted` with it's iv `iv`, key `key` and
     * salt `salt`.
     *
     * Required is that the cipher AES-256-CBC was used.
     *
     * @return Decrypted cipher text as plain String.
     */
    fun decrypt(encrypted: String, key: String, iv: ByteArray, salt: ByteArray): String {
        val key_bytes: ByteArray = hash(key.toCharArray(), salt).toByteArray(Charsets.UTF_8)
        val aes_key: ByteArray = ByteArray(32)

        // Now make the key bytes fit into 256
        if (key_bytes.size < aes_key.size) {
            throw Error("Tried to cut down hash bytes to 32 but there are less then that.")
        }
        for (i in 0..aes_key.size-1) {
            aes_key.set(i, key_bytes[i])
        }

        val encrypted_raw = Base64.decode(encrypted, Base64.NO_WRAP)

        Log.i("Crypto", "Decrypt using ${String(aes_key)} '${Base64.encodeToString(encrypted_raw, Base64.NO_WRAP)} (${encrypted_raw.size})' ${String(iv)}")

        val keySpec: SecretKey = SecretKeySpec(aes_key, "AES")
        val cipher: Cipher = Cipher.getInstance("AES/CBC/ISO10126Padding")

        // Performing actual crypto operation
        cipher.init(Cipher.DECRYPT_MODE, keySpec, IvParameterSpec(iv))
        val ciphered = cipher.doFinal(encrypted_raw)    // TODO: Catch error if decryption goes wrong

        // Concat everything into one byte array
        val byteBuffer: ByteBuffer = ByteBuffer.allocate(ciphered.size)
        byteBuffer.put(ciphered)

        Log.d("Crypto", "Decrypted $encrypted => ${String(byteBuffer.array())}")
        return String(byteBuffer.array())
    }

    /**
     * Generates a random IV used for encryption.
     *
     * @return Random IV of a length of 12 bytes
     */
    fun nextIV(): ByteArray {
        val iv: ByteArray = ByteArray(12)
        val random: SecureRandom = SecureRandom()
        random.nextBytes(iv)

        return iv
    }

    /**
     * Produces secure random string. <b>This function can be slow, use only if necessary!</b>
     */
    fun nextString(length: Int): String {
        chars = CharArray(length)
        for (idx in chars.indices) chars[idx] = possibleChars[random.nextInt(possibleChars.length)]
        Log.i("Random", "Produced random string: ${String(chars)}")

        return String(chars)
    }

}