package wang.yaojia.webterm.push

import android.os.Bundle
import android.util.Log
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators
import androidx.biometric.BiometricPrompt
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import wang.yaojia.webterm.api.models.HookDecision
import wang.yaojia.webterm.hostregistry.HostStore
import wang.yaojia.webterm.wiring.ApiClientFactory
import java.util.UUID
import javax.inject.Inject

/**
 * Hosts the **Allow** action's BiometricPrompt (plan §8 / R1 mustFix). A `BroadcastReceiver` or
 * `Service` CANNOT present a `BiometricPrompt` (it needs a `FragmentActivity` and would blow
 * `goAsync()`'s ~10 s budget), so Allow — unlike the auth-free Deny — trampolines through this
 * translucent, `excludeFromRecents` Activity (both set in the manifest). It shows only the system
 * biometric sheet; on success it fires `POST /hook/decision {decision:"allow"}`, then finishes.
 *
 * The single-use token arrives in the `FLAG_IMMUTABLE` PendingIntent extras and is handed straight
 * to [PushDecisionSubmitter] — never logged, never persisted, never rendered. Any cancel/error/no-auth
 * path finishes WITHOUT posting (fail-safe: no approval without a fresh biometric).
 */
@AndroidEntryPoint
public class AllowTrampolineActivity : FragmentActivity() {

    @Inject
    public lateinit var hostStore: HostStore

    @Inject
    public lateinit var apiClientFactory: ApiClientFactory

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val sessionId = intent.getStringExtra(NotificationBuilder.EXTRA_SESSION_ID)
            ?.let { runCatching { UUID.fromString(it) }.getOrNull() }
        val token = intent.getStringExtra(NotificationBuilder.EXTRA_TOKEN)
        val notificationId = intent.getIntExtra(NotificationBuilder.EXTRA_NOTIFICATION_ID, INVALID_ID)
        if (sessionId == null || token.isNullOrEmpty()) {
            Log.w(TAG, "allow: missing session id or token; finishing")
            finish()
            return
        }
        if (!canAuthenticate()) {
            Log.w(TAG, "allow: no biometric/credential available; finishing without approving")
            finish()
            return
        }
        promptThenAllow(sessionId, token, notificationId)
    }

    private fun promptThenAllow(sessionId: UUID, token: String, notificationId: Int) {
        val prompt = BiometricPrompt(
            this,
            ContextCompat.getMainExecutor(this),
            object : BiometricPrompt.AuthenticationCallback() {
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    approveThenFinish(sessionId, token, notificationId)
                }

                override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                    // User cancelled / lockout / hardware error → do NOT approve. Fail-safe.
                    Log.d(TAG, "allow: auth error $errorCode; finishing without approving")
                    finish()
                }
                // onAuthenticationFailed (a single non-match) intentionally left to retry in-prompt.
            },
        )
        prompt.authenticate(promptInfo())
    }

    private fun approveThenFinish(sessionId: UUID, token: String, notificationId: Int) {
        val submitter = PushDecisionSubmitter.fromRegistry(hostStore, apiClientFactory) { Log.d(TAG, it) }
        lifecycleScope.launch {
            // mTLS build + network off the main thread; finish back on main.
            withContext(Dispatchers.IO) { submitter.submit(sessionId, HookDecision.ALLOW, token) }
            NotificationManagerCompat.from(this@AllowTrampolineActivity).cancel(notificationId)
            finish()
        }
    }

    private fun canAuthenticate(): Boolean =
        BiometricManager.from(this).canAuthenticate(ALLOWED_AUTHENTICATORS) ==
            BiometricManager.BIOMETRIC_SUCCESS

    private fun promptInfo(): BiometricPrompt.PromptInfo =
        BiometricPrompt.PromptInfo.Builder()
            .setTitle(PROMPT_TITLE)
            .setSubtitle(PROMPT_SUBTITLE)
            .setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
            .setNegativeButtonText(PROMPT_NEGATIVE)
            .build()

    private companion object {
        const val TAG = "AllowTrampoline"
        const val INVALID_ID = -1
        // BIOMETRIC_STRONG only (+ a negative button) — the one combination valid on API 29+.
        // Device-credential fallback is a §7 device-QA refinement (can't mix with a negative button).
        const val ALLOWED_AUTHENTICATORS = Authenticators.BIOMETRIC_STRONG
        const val PROMPT_TITLE = "Approve action"
        const val PROMPT_SUBTITLE = "Confirm to allow the pending action"
        const val PROMPT_NEGATIVE = "Cancel"
    }
}
