/*
 * Copyright 2025 Signal Messenger, LLC
 * SPDX-License-Identifier: AGPL-3.0-only
 */

package org.thoughtcrime.securesms

import android.content.Context
import android.os.Environment
import android.preference.PreferenceManager
import android.util.Base64
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import org.signal.core.models.AccountEntropyPool
import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.IdentityKeyPair
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.RestoreDecisionState
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.keyvalue.Skipped
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.registration.data.AccountRegistrationResult
import org.thoughtcrime.securesms.registration.data.LocalRegistrationMetadataUtil
import org.thoughtcrime.securesms.registration.data.QuickstartCredentials
import org.thoughtcrime.securesms.registration.data.RegistrationData
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
import org.thoughtcrime.securesms.registration.util.RegistrationUtil
import org.whispersystems.signalservice.api.account.PreKeyCollection
import java.io.File

/**
 * Reads pre-baked registration credentials from assets and performs
 * local registration, bypassing the normal registration flow.
 *
 * Follows the same pattern as [org.signal.benchmark.setup.TestUsers.setupSelf].
 */
object QuickstartInitializer {

  private val TAG = Log.tag(QuickstartInitializer::class.java)

  var pendingBackupDir: File? = null

  fun initialize(context: Context) {
    val credentialJson = findCredentialJson(context)
    if (credentialJson == null) {
      Log.w(TAG, "No quickstart credentials found on disk or in assets. Falling through to normal registration.")
      return
    }

    val credentials = Json.decodeFromString<QuickstartCredentials>(credentialJson)
    Log.i(TAG, "Loaded quickstart credentials for ${credentials.e164}")

    // Restore AEP before any derived keys are accessed
    if (credentials.accountEntropyPool != null) {
      SignalStore.account.restoreAccountEntropyPool(AccountEntropyPool(credentials.accountEntropyPool))
      Log.i(TAG, "Restored account entropy pool from quickstart credentials")
    }

    // Master secret setup
    PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean("pref_prompted_push_registration", true).commit()
    val masterSecret = MasterSecretUtil.generateMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE)
    MasterSecretUtil.generateAsymmetricMasterSecret(context, masterSecret)
    context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0).edit().putBoolean("passphrase_initialized", true).commit()

    // Set registration IDs from credentials
    SignalStore.account.registrationId = credentials.registrationId
    SignalStore.account.pniRegistrationId = credentials.pniRegistrationId

    // Decode pre-baked keys
    val aciIdentityKeyPair = IdentityKeyPair(Base64.decode(credentials.aciIdentityKeyPair, Base64.DEFAULT))
    val pniIdentityKeyPair = IdentityKeyPair(Base64.decode(credentials.pniIdentityKeyPair, Base64.DEFAULT))
    val aciSignedPreKey = SignedPreKeyRecord(Base64.decode(credentials.aciSignedPreKey, Base64.DEFAULT))
    val aciLastResortKyberPreKey = KyberPreKeyRecord(Base64.decode(credentials.aciLastResortKyberPreKey, Base64.DEFAULT))
    val pniSignedPreKey = SignedPreKeyRecord(Base64.decode(credentials.pniSignedPreKey, Base64.DEFAULT))
    val pniLastResortKyberPreKey = KyberPreKeyRecord(Base64.decode(credentials.pniLastResortKyberPreKey, Base64.DEFAULT))
    val profileKey = ProfileKey(Base64.decode(credentials.profileKey, Base64.DEFAULT))

    val registrationData = RegistrationData(
      code = "000000",
      e164 = credentials.e164,
      password = credentials.servicePassword,
      registrationId = credentials.registrationId,
      profileKey = profileKey,
      fcmToken = null,
      pniRegistrationId = credentials.pniRegistrationId,
      recoveryPassword = null
    )

    val remoteResult = AccountRegistrationResult(
      uuid = credentials.aci,
      pni = credentials.pni,
      storageCapable = false,
      number = credentials.e164,
      masterKey = null,
      pin = null,
      aciPreKeyCollection = PreKeyCollection(aciIdentityKeyPair.publicKey, aciSignedPreKey, aciLastResortKyberPreKey),
      pniPreKeyCollection = PreKeyCollection(pniIdentityKeyPair.publicKey, pniSignedPreKey, pniLastResortKyberPreKey),
      reRegistration = false
    )

    // Create metadata and register locally
    val localRegistrationData = LocalRegistrationMetadataUtil.createLocalRegistrationMetadata(
      aciIdentityKeyPair,
      pniIdentityKeyPair,
      registrationData,
      remoteResult,
      false
    )

    runBlocking {
      RegistrationRepository.registerAccountLocally(context, localRegistrationData)
    }

    // Enable FCM so the app fetches a token through its normal startup flow
    // rather than keeping a websocket open.
    SignalStore.account.fcmEnabled = true

    // Finalize registration state
    SignalStore.svr.optOut()
    SignalStore.registration.restoreDecisionState = RestoreDecisionState.Skipped
    SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts(credentials.profileGivenName, credentials.profileFamilyName))
    RegistrationUtil.maybeMarkRegistrationComplete()

    // Check for a local backup to restore
    val backupsDir = File(DISK_CREDENTIALS_DIR, "SignalBackups")
    if (backupsDir.exists() && backupsDir.isDirectory) {
      val hasSnapshot = backupsDir.listFiles()?.any { it.isDirectory && it.name.startsWith("signal-backup") } == true
      if (hasSnapshot) {
        Log.i(TAG, "Found local backup directory at ${backupsDir.absolutePath}, will restore after launch")
        pendingBackupDir = DISK_CREDENTIALS_DIR
      }
    }

    Log.i(TAG, "Quickstart initialization complete for ${credentials.e164}")
  }

  private val DISK_CREDENTIALS_DIR = File(Environment.getExternalStorageDirectory(), "signal-quickstart")

  private val diskCredentialFileName: String
    get() = if (org.thoughtcrime.securesms.util.Environment.IS_STAGING) "staging_credentials.json" else "prod_credentials.json"

  private fun findCredentialJson(context: Context): String? {
    return findCredentialJsonOnDisk() ?: findCredentialJsonInAssets(context)
  }

  private fun findCredentialJsonOnDisk(): String? {
    val file = File(DISK_CREDENTIALS_DIR, diskCredentialFileName)
    return try {
      if (file.exists()) {
        Log.i(TAG, "Found quickstart credentials on disk at ${file.absolutePath}")
        file.readText()
      } else {
        Log.d(TAG, "No quickstart credentials found on disk at ${file.absolutePath}")
        null
      }
    } catch (e: Exception) {
      Log.w(TAG, "Error reading quickstart credentials from disk", e)
      null
    }
  }

  private fun findCredentialJsonInAssets(context: Context): String? {
    return try {
      val files = context.assets.list("quickstart") ?: return null
      val jsonFile = files.firstOrNull { it.endsWith(".json") } ?: return null
      context.assets.open("quickstart/$jsonFile").bufferedReader().readText()
    } catch (e: Exception) {
      Log.w(TAG, "Error reading quickstart credentials from assets", e)
      null
    }
  }
}
