package jp.juggler.subwaytooter.api.auth

import android.net.Uri
import androidx.core.net.toUri
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.subwaytooter.api.send
import jp.juggler.subwaytooter.column.encodeQuery
import jp.juggler.util.data.JsonObject
import jp.juggler.util.data.buildJsonObject
import jp.juggler.util.data.jsonObjectOf
import jp.juggler.util.data.notEmpty
import jp.juggler.util.network.toFormRequestBody
import jp.juggler.util.network.toPost
import okhttp3.Request

class ApiAuthMastodon(
    val client: TootApiClient,
) {
    /**
     * クライアントアプリをサーバに登録する
     */
    suspend fun registerClient(
        apiHost: Host,
        scopeString: String,
        clientName: String,
        callbackUrl: String,
    ): JsonObject = jsonObjectOf(
        "client_name" to clientName,
        "redirect_uris" to callbackUrl,
        "scopes" to scopeString,
    ).encodeQuery().toFormRequestBody().toPost()
        .url("https://${apiHost.ascii}/api/v1/apps")
        .build()
        .send(client, errorSuffix = apiHost.pretty)
        .readJsonObject()

    /**
     * サーバ上に登録されたアプリを参照する client credential を作成する
     */
    suspend fun createClientCredential(
        apiHost: Host,
        clientId: String,
        clientSecret: String,
        callbackUrl: String,
    ): JsonObject = buildJsonObject {
        put("grant_type", "client_credentials")
        put("scope", "read write") // 空白は + に変換されること
        put("client_id", clientId)
        put("client_secret", clientSecret)
        put("redirect_uri", callbackUrl)
    }.encodeQuery().toFormRequestBody().toPost()
        .url("https://${apiHost.ascii}/oauth/token")
        .build()
        .send(client, errorSuffix = apiHost.pretty)
        .readJsonObject()

    /**
     * client credentialを使って、サーバ上に登録されたクライアントアプリの情報を取得する
     * - クライアント情報がまだ有効か調べるのに使う
     */
    // client_credentialがまだ有効か調べる
    suspend fun verifyClientCredential(
        apiHost: Host,
        clientCredential: String,
    ): JsonObject = Request.Builder()
        .url("https://${apiHost.ascii}/api/v1/apps/verify_credentials")
        .header("Authorization", "Bearer $clientCredential")
        .build()
        .send(client, errorSuffix = apiHost.pretty)
        .readJsonObject()

    /**
     * client credentialを削除する
     * - クライアント情報そのものは消えない…
     */
    suspend fun revokeClientCredential(
        apiHost: Host,
        clientId: String,
        clientSecret: String,
        clientCredential: String,
    ): JsonObject = buildJsonObject {
        put("client_id", clientId)
        put("client_secret", clientSecret)
        put("token", clientCredential)
    }.encodeQuery().toFormRequestBody().toPost()
        .url("https://${apiHost.ascii}/oauth/revoke")
        .build()
        .send(client, errorSuffix = apiHost.pretty)
        .readJsonObject()

    // 認証ページURLを作る
    fun createAuthUrl(
        apiHost: Host,
        scopeString: String,
        callbackUrl: String,
        clientId: String,
        state: String,
    ): Uri = buildJsonObject {
        put("client_id", clientId)
        put("response_type", "code")
        put("redirect_uri", callbackUrl)
        put("scope", scopeString)
        put("state", state)
        put("grant_type", "authorization_code")
        put("approval_prompt", "force")
        put("force_login", "true")
        //		+"&access_type=offline"
    }.encodeQuery()
        .let { "https://${apiHost.ascii}/oauth/authorize?$it" }
        .toUri()

    /**
     * ブラウザから帰ってきたコードを使い、認証の続きを行う
     */
    suspend fun authStep2(
        apiHost: Host,
        clientId: String,
        clientSecret: String,
        scopeString: String,
        callbackUrl: String,
        code: String,
    ): JsonObject = jsonObjectOf(
        "grant_type" to "authorization_code",
        "code" to code,
        "client_id" to clientId,
        "client_secret" to clientSecret,
        "scope" to scopeString,
        "redirect_uri" to callbackUrl,
    ).encodeQuery().toFormRequestBody().toPost()
        .url("https://${apiHost.ascii}/oauth/token")
        .build()
        .send(client)
        .readJsonObject()

    // 認証されたアカウントのユーザ情報を取得する
    suspend fun verifyAccount(
        apiHost: Host,
        accessToken: String,
    ): JsonObject = Request.Builder()
        .url("https://${apiHost.ascii}/api/v1/accounts/verify_credentials")
        .header("Authorization", "Bearer $accessToken")
        .build()
        .send(client, errorSuffix = apiHost.pretty)
        .readJsonObject()

    /**
     * ユーザ登録API
     * アクセストークンはあるがアカウントIDがない状態になる。
     */
    suspend fun createUser(
        apiHost: Host,
        clientCredential: String,
        params: CreateUserParams,
    ) = buildJsonObject {
        put("username", params.username)
        put("email", params.email)
        put("password", params.password)
        put("agreement", params.agreement)
        params.reason?.notEmpty()?.let { put("reason", it) }
    }.encodeQuery().toFormRequestBody().toPost()
        .url("https://${apiHost.ascii}/api/v1/accounts")
        .header("Authorization", "Bearer $clientCredential")
        .build()
        .send(client, errorSuffix = apiHost.pretty)
        .readJsonObject()
}
