package app.closer.data.security

import android.content.Context
import android.util.Base64
import app.closer.BuildConfig
import app.closer.domain.security.DeviceIntegrityChecker
import app.closer.domain.security.DeviceIntegrityResult
import com.google.android.play.core.integrity.IntegrityManagerFactory
import com.google.android.play.core.integrity.IntegrityTokenRequest
import com.google.firebase.functions.FirebaseFunctions
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

/**
 * Checks device integrity using the Play Integrity API.
 *
 * Flow:
 * 1. Requests a signed integrity token from the Play Integrity API on-device.
 * 2. Sends the token to the [checkDeviceIntegrity] Cloud Function for server-side
 *    verification via the Google Play Integrity API.
 * 3. Exposes the verdict through [state].
 *
 * The check runs once at app startup. A [COMPROMISED] result does not hard-block
 * the app — it surfaces a warning so users on unintentionally modified devices
 * can investigate. Firebase App Check (Play Integrity) already blocks Firebase
 * API calls from compromised environments; this provides an in-app signal.
 *
 * In debug builds the check is skipped (many dev devices have unlocked bootloaders).
 *
 * Setup required:
 * - Enable the Play Integrity API in your Google Cloud project.
 * - Ensure the Cloud Functions service account has the "Play Integrity API User" role,
 *   or link the Cloud project to your Play Console app in the Play Console settings.
 */
@Singleton
class PlayIntegrityChecker @Inject constructor(
    @ApplicationContext private val context: Context,
    private val functions: FirebaseFunctions,
) : DeviceIntegrityChecker {

    private val _state = MutableStateFlow(DeviceIntegrityResult.UNKNOWN)
    override val state: StateFlow<DeviceIntegrityResult> = _state.asStateFlow()

    override suspend fun runCheck() {
        if (BuildConfig.DEBUG) {
            _state.value = DeviceIntegrityResult.UNAVAILABLE
            return
        }

        val token = try {
            requestToken()
        } catch (_: Exception) {
            _state.value = DeviceIntegrityResult.UNAVAILABLE
            return
        }

        // Security review Batch 3: don't fail-open to PASSED when verification can't be
        // completed. A null verdict (server/network error, or no verdict returned) maps to
        // UNAVAILABLE — neutral, not a false "passed". Firebase App Check remains the real
        // server-side gatekeeper; this is only an in-app signal.
        _state.value = when (verifyWithServer(token)) {
            true -> DeviceIntegrityResult.PASSED
            false -> DeviceIntegrityResult.COMPROMISED
            null -> DeviceIntegrityResult.UNAVAILABLE
        }
    }

    private suspend fun requestToken(): String = suspendCancellableCoroutine { cont ->
        val nonce = Base64.encodeToString(
            "${UUID.randomUUID()}-${System.currentTimeMillis()}".toByteArray(Charsets.UTF_8),
            Base64.URL_SAFE or Base64.NO_WRAP,
        )
        IntegrityManagerFactory.create(context)
            .requestIntegrityToken(IntegrityTokenRequest.builder().setNonce(nonce).build())
            .addOnSuccessListener { cont.resume(it.token()) }
            .addOnFailureListener { cont.resumeWithException(it) }
    }

    /** Returns the server verdict, or null when verification couldn't be completed. */
    private suspend fun verifyWithServer(token: String): Boolean? =
        suspendCancellableCoroutine { cont ->
            functions
                .getHttpsCallable("checkDeviceIntegrity")
                .call(mapOf("token" to token))
                .addOnSuccessListener { result ->
                    val passed = (result.getData() as? Map<*, *>)?.get("passed") as? Boolean
                    cont.resume(passed) // null if the server returned no explicit verdict
                }
                .addOnFailureListener {
                    // Couldn't verify (server/network). Don't assert PASSED — surface as
                    // UNAVAILABLE. Firebase App Check remains the server-side gatekeeper.
                    cont.resume(null)
                }
        }
}
