package jp.juggler.subwaytooter.search

import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.addWithFilterStatus
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.api.entity.EntityId
import jp.juggler.subwaytooter.api.entity.ServiceType
import jp.juggler.util.*
import okhttp3.Request

object MspHelper {
    private const val mspTokenUrl = "https://msearch.fediverse.media/api/v1.0.1/utoken"
    private const val mspSearchUrl = "https://msearch.fediverse.media/api/v1.0.1/cross"
    private const val mspApiKey = "e53de7f66130208f62d1808672bf6320523dcd0873dc69bc"

    // 検索結果からページネーション用IDを抽出する
    // MSPでは終端は分からないので、配列が空でも次回また同じ位置から読み直す
    private fun getNextId(array: JsonArray, old: String?) =
        array.lastOrNull().cast<JsonObject>()?.string("msp_id")?.notEmpty() ?: old

    private suspend fun TootApiClient.search(query: String, maxId: String?): TootApiResult? {

        // ユーザトークンを読む
        var user_token: String? = PrefS.spMspUserToken(pref)

        for (nTry in 0 until 3) {
            if (callback.isApiCancelled) return null

            // ユーザトークンがなければ取得する
            if (user_token == null || user_token.isEmpty()) {

                callback.publishApiProgress("get MSP user token...")

                val result: TootApiResult = TootApiResult.makeWithCaption("Mastodon Search Portal")
                if (result.error != null) return result

                if (!sendRequest(result) {
                        Request.Builder()
                            .url(mspTokenUrl + "?apikey=" + mspApiKey.encodePercent())
                            .build()
                    }) return result

                val r2 = parseJson(result) { json ->
                    val error = json.string("error")
                    if (error == null) {
                        null
                    } else {
                        val type = json.string("type")
                        "error: $type $error"
                    }
                }
                val jsonObject = r2?.jsonObject ?: return r2
                user_token = jsonObject.jsonObject("result")?.string("token")
                if (user_token?.isEmpty() != false) {
                    return result.setError("Can't get MSP user token. response=${result.bodyString}")
                } else {
                    pref.edit().put(PrefS.spMspUserToken, user_token).apply()
                }
            }

            // ユーザトークンを使って検索APIを呼び出す
            val result: TootApiResult = TootApiResult.makeWithCaption("Mastodon Search Portal")
            if (result.error != null) return result

            if (!sendRequest(result) {
                    val url = StringBuilder()
                        .append(mspSearchUrl)
                        .append("?apikey=").append(mspApiKey.encodePercent())
                        .append("&utoken=").append(user_token.encodePercent())
                        .append("&q=").append(query.encodePercent())
                        .append("&max=").append(maxId?.encodePercent() ?: "")

                    Request.Builder().url(url.toString()).build()
                }) return result

            var isUserTokenError = false
            val r2 = parseJson(result) { json ->
                val error = json.string("error")
                if (error == null) {
                    null
                } else {
                    // ユーザトークンがダメなら生成しなおす
                    val detail = json.string("detail")
                    if ("utoken" == detail) {
                        isUserTokenError = true
                    }

                    val type = json.string("type")
                    "API returns error: $type $error"
                }
            }
            if (r2 == null || !isUserTokenError) return r2
        }
        return TootApiResult("MSP user token retry exceeded.")
    }

    private fun parseList(parser: TootParser, root: JsonArray) =
        parser.apply { serviceType = ServiceType.MSP }.statusList(root)

    suspend fun ColumnTask_Loading.loadingMSP(client: TootApiClient): TootApiResult? {
        column.idOld = null
        val q = column.searchQuery.trim { it <= ' ' }
        return if (q.isEmpty()) {
            listTmp = java.util.ArrayList()
            TootApiResult()
        } else {
            client.search(column.searchQuery, column.idOld?.toString())?.also { result ->
                result.jsonArray?.let { root ->
                    column.idOld = EntityId.mayNull(getNextId(root, null))
                    listTmp = addWithFilterStatus(null, parseList(parser, root))
                }
            }
        }
    }

    suspend fun ColumnTask_Refresh.refreshMSP(client: TootApiClient): TootApiResult? {
        if (!bBottom) return TootApiResult("head of list.")

        val q = column.searchQuery.trim()
        val old = column.idOld?.toString()
        return if (q.isEmpty() || old == null) {
            listTmp = ArrayList()
            TootApiResult(context.getString(R.string.end_of_list))
        } else {
            client.search(q, old)?.also { result ->
                result.jsonArray?.let { root ->
                    column.idOld = EntityId.mayNull(getNextId(root, column.idOld?.toString()))
                    listTmp = addWithFilterStatus(listTmp, parseList(parser, root))
                }
            }
        }
    }
}
