package cash.z.ecc.sdk.model

import android.app.Application
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toEntropy
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
import cash.z.ecc.android.sdk.type.WalletBirthday
import cash.z.ecc.android.sdk.type.ZcashNetwork
import cash.z.ecc.sdk.type.fromResources
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject

/**
 * Represents everything needed to save and restore a wallet.
 */
data class PersistableWallet(
    val network: ZcashNetwork,
    val birthday: WalletBirthday?,
    val seedPhrase: SeedPhrase
) {

    /**
     * @return Wallet serialized to JSON format, suitable for long-term encrypted storage.
     */
    // Note: We're using a hand-crafted serializer so that we're less likely to have accidental
    // breakage from reflection or annotation based methods, and so that we can carefully manage versioning.
    fun toJson() = JSONObject().apply {
        put(KEY_VERSION, VERSION_1)
        put(KEY_NETWORK_ID, network.id)
        birthday?.let {
            put(KEY_BIRTHDAY, it.toJson())
        }
        put(KEY_SEED_PHRASE, seedPhrase.joinToString())
    }

    // For security, intentionally override the toString method to reduce risk of accidentally logging secrets
    override fun toString() = "PersistableWallet"

    companion object {
        private const val VERSION_1 = 1

        internal const val KEY_VERSION = "v"
        internal const val KEY_NETWORK_ID = "network_ID"
        internal const val KEY_BIRTHDAY = "birthday"
        internal const val KEY_SEED_PHRASE = "seed_phrase"

        fun from(jsonObject: JSONObject): PersistableWallet {
            when (val version = jsonObject.getInt(KEY_VERSION)) {
                VERSION_1 -> {
                    val networkId = jsonObject.getInt(KEY_NETWORK_ID)
                    val birthday = if (jsonObject.has(KEY_BIRTHDAY)) {
                        WalletBirthday.from(jsonObject.getJSONObject(KEY_BIRTHDAY))
                    } else {
                        null
                    }
                    val seedPhrase = jsonObject.getString(KEY_SEED_PHRASE)

                    return PersistableWallet(ZcashNetwork.from(networkId), birthday, SeedPhrase.new(seedPhrase))
                }
                else -> {
                    throw IllegalArgumentException("Unsupported version $version")
                }
            }
        }

        /**
         * @return A new PersistableWallet with a random seed phrase.
         */
        suspend fun new(application: Application): PersistableWallet {
            val zcashNetwork = ZcashNetwork.fromResources(application)
            val walletBirthday = WalletBirthdayTool.loadNearest(application, zcashNetwork)

            val seedPhrase = newSeedPhrase()

            return PersistableWallet(zcashNetwork, walletBirthday, seedPhrase)
        }
    }
}

// Using IO context because of https://github.com/zcash/kotlin-bip39/issues/13
private suspend fun newMnemonic() = withContext(Dispatchers.IO) {
    Mnemonics.MnemonicCode(cash.z.ecc.android.bip39.Mnemonics.WordCount.COUNT_24.toEntropy()).words
}

private suspend fun newSeedPhrase() = SeedPhrase(newMnemonic().map { it.concatToString() })
