package com.bytelyst.platform

import android.content.Context
import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.CredentialManager
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetPublicKeyCredentialOption
import androidx.credentials.PublicKeyCredential
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive

/**
 * Passkey manager wrapping Android Credential Manager API.
 *
 * Handles FIDO2/WebAuthn passkey registration and authentication
 * by coordinating between the platform-service backend and the
 * Android Credential Manager.
 *
 * Usage:
 * ```kotlin
 * val manager = BLPasskeyManager(context, authClient)
 * // Register a new passkey
 * manager.registerPasskey("My Pixel 9")
 * // Authenticate with an existing passkey
 * val user = manager.authenticateWithPasskey()
 * ```
 */
class BLPasskeyManager(
    private val context: Context,
    private val authClient: BLAuthClient,
) {
    private val credentialManager = CredentialManager.create(context)
    private val json get() = authClient.client.json

    /**
     * Register a new passkey for the current user.
     *
     * 1. Fetches registration options from backend
     * 2. Invokes Credential Manager to create credential
     * 3. Sends attestation response to backend for verification
     *
     * @param friendlyName Human-readable name for this passkey (e.g. "Pixel 9")
     * @throws Exception if any step fails
     */
    suspend fun registerPasskey(friendlyName: String) {
        // Step 1: Get registration options from backend
        val optionsResponse = authClient.client.request(
            "POST",
            "/api/auth/passkeys/register/options",
        )

        // Step 2: Create credential via Credential Manager
        val request = CreatePublicKeyCredentialRequest(
            requestJson = optionsResponse,
        )
        val result = credentialManager.createCredential(context, request)
        val credential = result as? androidx.credentials.CreatePublicKeyCredentialResponse
            ?: throw IllegalStateException("Unexpected credential type")

        // Step 3: Send attestation to backend
        val attestationJson = credential.registrationResponseJson
        // Append friendlyName to the response
        val bodyObj = json.decodeFromString<JsonObject>(attestationJson)
        val mutableMap = bodyObj.toMutableMap()
        mutableMap["friendlyName"] = kotlinx.serialization.json.JsonPrimitive(friendlyName)
        val body = json.encodeToString(JsonObject.serializer(), JsonObject(mutableMap))

        authClient.client.request(
            "POST",
            "/api/auth/passkeys/register/verify",
            body,
        )
    }

    /**
     * Authenticate using an existing passkey.
     *
     * 1. Fetches authentication options from backend
     * 2. Invokes Credential Manager to select and sign with credential
     * 3. Sends assertion response to backend for verification
     * 4. Returns authenticated user and stores tokens
     *
     * @return Authenticated user
     * @throws Exception if any step fails
     */
    suspend fun authenticateWithPasskey(): BLAuthClient.AuthUser {
        // Step 1: Get authentication options from backend
        val optionsResponse = authClient.client.request(
            "POST",
            "/api/auth/passkeys/authenticate/options",
            skipAuth = true,
        )

        // Step 2: Get credential via Credential Manager
        val getRequest = GetCredentialRequest(
            listOf(GetPublicKeyCredentialOption(requestJson = optionsResponse)),
        )
        val result = credentialManager.getCredential(context, getRequest)
        val credential = result.credential as? PublicKeyCredential
            ?: throw IllegalStateException("Unexpected credential type")

        // Step 3: Send assertion to backend
        val assertionJson = credential.authenticationResponseJson
        val response = authClient.client.request(
            "POST",
            "/api/auth/passkeys/authenticate/verify",
            assertionJson,
            skipAuth = true,
        )

        // Step 4: Parse tokens and update auth state
        val tokenResult = json.decodeFromString<BLAuthClient.TokenResponse>(response)
        // Use reflection-free approach: directly set tokens
        authClient.handleLoginResult(tokenResult)
        return tokenResult.user
    }

    /**
     * List registered passkeys for the current user.
     */
    suspend fun listPasskeys(): List<BLAuthClient.Passkey> {
        val response = authClient.client.request("GET", "/api/auth/passkeys")
        return json.decodeFromString<List<BLAuthClient.Passkey>>(response)
    }

    /**
     * Delete a passkey (requires step-up authentication).
     */
    suspend fun deletePasskey(passkeyId: String) {
        authClient.client.request("DELETE", "/api/auth/passkeys/$passkeyId")
    }
}
