package com.syncflow.ui.auth

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Base64
import androidx.browser.customtabs.CustomTabsIntent
import com.syncflow.data.security.CredentialStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.FormBody
import okhttp3.OkHttpClient
import okhttp3.Request
import java.security.MessageDigest
import java.security.SecureRandom

const val OAUTH_REDIRECT_ACTION = "com.syncflow.OAUTH_RESULT"
const val OAUTH_EXTRA_TOKEN = "token"
const val OAUTH_EXTRA_EMAIL = "email"
const val OAUTH_EXTRA_PROVIDER = "provider"

private val client = OkHttpClient()
private val random = SecureRandom()

private fun generateVerifier(): String {
    val bytes = ByteArray(32)
    random.nextBytes(bytes)
    return Base64.encodeToString(bytes, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
}

private fun generateChallenge(verifier: String): String {
    val digest = MessageDigest.getInstance("SHA-256").digest(verifier.toByteArray(Charsets.US_ASCII))
    return Base64.encodeToString(digest, Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
}

fun launchDropboxOAuth(context: Context, credentialStore: CredentialStore, appKey: String) {
    val verifier = generateVerifier()
    val state = generateVerifier()
    credentialStore.savePkceVerifier("dropbox", verifier)
    credentialStore.savePkceVerifier("dropbox_state", state)
    val challenge = generateChallenge(verifier)
    val url = "https://www.dropbox.com/oauth2/authorize" +
        "?client_id=$appKey" +
        "&response_type=code" +
        "&redirect_uri=syncflow%3A%2F%2Foauth%2Fdropbox" +
        "&code_challenge=$challenge" +
        "&code_challenge_method=S256" +
        "&token_access_type=offline" +
        "&state=$state"
    openCustomTab(context, url)
}

fun launchOneDriveOAuth(context: Context, credentialStore: CredentialStore, clientId: String) {
    val verifier = generateVerifier()
    val state = generateVerifier()
    credentialStore.savePkceVerifier("onedrive", verifier)
    credentialStore.savePkceVerifier("onedrive_state", state)
    val challenge = generateChallenge(verifier)
    val scopes = "Files.ReadWrite+User.Read+offline_access"
    val url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize" +
        "?client_id=$clientId" +
        "&response_type=code" +
        "&redirect_uri=syncflow%3A%2F%2Foauth%2Fonedrive" +
        "&scope=$scopes" +
        "&code_challenge=$challenge" +
        "&code_challenge_method=S256" +
        "&state=$state"
    openCustomTab(context, url)
}

private fun openCustomTab(context: Context, url: String) {
    try {
        CustomTabsIntent.Builder().build().launchUrl(context, Uri.parse(url))
    } catch (_: Exception) {
        context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK })
    }
}

suspend fun exchangeDropboxCode(
    credentialStore: CredentialStore,
    code: String,
    appKey: String,
): Pair<String, String>? = withContext(Dispatchers.IO) {
    val verifier = credentialStore.getPkceVerifier("dropbox") ?: return@withContext null
    credentialStore.removePkceVerifier("dropbox")
    val body = FormBody.Builder()
        .add("code", code)
        .add("grant_type", "authorization_code")
        .add("client_id", appKey)
        .add("redirect_uri", "syncflow://oauth/dropbox")
        .add("code_verifier", verifier)
        .build()
    val req = Request.Builder().url("https://api.dropboxapi.com/oauth2/token").post(body).build()
    val resp = runCatching { client.newCall(req).execute() }.getOrNull() ?: return@withContext null
    val text = resp.body?.string() ?: return@withContext null
    val json = runCatching { Json.parseToJsonElement(text).jsonObject }.getOrNull() ?: return@withContext null
    val token = json["access_token"]?.jsonPrimitive?.content ?: return@withContext null
    val email = json["account_id"]?.jsonPrimitive?.content ?: ""
    Pair(token, email)
}

suspend fun exchangeOneDriveCode(
    credentialStore: CredentialStore,
    code: String,
    clientId: String,
): Pair<String, String>? = withContext(Dispatchers.IO) {
    val verifier = credentialStore.getPkceVerifier("onedrive") ?: return@withContext null
    credentialStore.removePkceVerifier("onedrive")
    val body = FormBody.Builder()
        .add("code", code)
        .add("grant_type", "authorization_code")
        .add("client_id", clientId)
        .add("redirect_uri", "syncflow://oauth/onedrive")
        .add("code_verifier", verifier)
        .add("scope", "Files.ReadWrite User.Read offline_access")
        .build()
    val req = Request.Builder().url("https://login.microsoftonline.com/common/oauth2/v2.0/token").post(body).build()
    val resp = runCatching { client.newCall(req).execute() }.getOrNull() ?: return@withContext null
    val text = resp.body?.string() ?: return@withContext null
    val json = runCatching { Json.parseToJsonElement(text).jsonObject }.getOrNull() ?: return@withContext null
    val token = json["access_token"]?.jsonPrimitive?.content ?: return@withContext null
    Pair(token, "")
}
