/*
 * Copyright (c) 2025 Element Creations Ltd.
 * Copyright 2023-2025 New Vector Ltd.
 *
 * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
 * Please see LICENSE files in the repository root for full details.
 */

package id.databisnis.android.bachot.features.lockscreen.impl.pin

import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.SingleIn
import id.databisnis.android.bachot.features.lockscreen.impl.storage.LockScreenStore
import id.databisnis.android.bachot.libraries.cryptography.api.EncryptionDecryptionService
import id.databisnis.android.bachot.libraries.cryptography.api.EncryptionResult
import id.databisnis.android.bachot.libraries.cryptography.api.SecretKeyRepository
import kotlinx.coroutines.flow.Flow
import java.util.concurrent.CopyOnWriteArrayList

private const val SECRET_KEY_ALIAS = "elementx.SECRET_KEY_ALIAS_PIN_CODE"

@ContributesBinding(AppScope::class)
@SingleIn(AppScope::class)
class DefaultPinCodeManager(
    private val secretKeyRepository: SecretKeyRepository,
    private val encryptionDecryptionService: EncryptionDecryptionService,
    private val lockScreenStore: LockScreenStore,
) : PinCodeManager {
    private val callbacks = CopyOnWriteArrayList<PinCodeManager.Callback>()

    override fun addCallback(callback: PinCodeManager.Callback) {
        callbacks.add(callback)
    }

    override fun removeCallback(callback: PinCodeManager.Callback) {
        callbacks.remove(callback)
    }

    override fun hasPinCode(): Flow<Boolean> {
        return lockScreenStore.hasPinCode()
    }

    override suspend fun getPinCodeSize(): Int {
        val encryptedPinCode = lockScreenStore.getEncryptedCode() ?: return 0
        val secretKey = secretKeyRepository.getOrCreateKey(SECRET_KEY_ALIAS, false)
        val decryptedPinCode = encryptionDecryptionService.decrypt(secretKey, EncryptionResult.fromBase64(encryptedPinCode))
        return decryptedPinCode.size
    }

    override suspend fun createPinCode(pinCode: String) {
        val secretKey = secretKeyRepository.getOrCreateKey(SECRET_KEY_ALIAS, false)
        val encryptedPinCode = encryptionDecryptionService.encrypt(secretKey, pinCode.toByteArray()).toBase64()
        lockScreenStore.saveEncryptedPinCode(encryptedPinCode)
        callbacks.forEach { it.onPinCodeCreated() }
    }

    override suspend fun verifyPinCode(pinCode: String): Boolean {
        val encryptedPinCode = lockScreenStore.getEncryptedCode() ?: return false
        return try {
            val secretKey = secretKeyRepository.getOrCreateKey(SECRET_KEY_ALIAS, false)
            val decryptedPinCode = encryptionDecryptionService.decrypt(secretKey, EncryptionResult.fromBase64(encryptedPinCode))
            val pinCodeToCheck = pinCode.toByteArray()
            decryptedPinCode.contentEquals(pinCodeToCheck).also { isPinCodeCorrect ->
                if (isPinCodeCorrect) {
                    lockScreenStore.resetCounter()
                    callbacks.forEach { callback ->
                        callback.onPinCodeVerified()
                    }
                } else {
                    lockScreenStore.onWrongPin()
                }
            }
        } catch (failure: Throwable) {
            false
        }
    }

    override suspend fun deletePinCode() {
        lockScreenStore.deleteEncryptedPinCode()
        lockScreenStore.resetCounter()
        callbacks.forEach { it.onPinCodeRemoved() }
    }

    override suspend fun getRemainingPinCodeAttemptsNumber(): Int {
        return lockScreenStore.getRemainingPinCodeAttemptsNumber()
    }
}
