package jp.juggler.subwaytooter.action

import android.content.Context
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.dialog.ActionsDialog
import jp.juggler.subwaytooter.table.AcctColor
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.util.matchHost
import jp.juggler.subwaytooter.util.openCustomTab
import jp.juggler.util.*
import java.util.*

private val log = LogCategory("Action_Conversation")

fun ActMain.clickConversation(
    pos: Int,
    accessInfo: SavedAccount,

    // optional. 未読表示のクリアに使う
    listAdapter: ItemListAdapter? = null,

    // どちらか非nullであること
    status: TootStatus? = null,
    summary: TootConversationSummary? = null,
) {
    // 未読クリアと表示の更新
    (summary ?: status?.conversationSummary)?.let {
        if (conversationUnreadClear(accessInfo, it)) {
            listAdapter?.notifyChange(
                reason = "ConversationSummary reset unread",
                reset = true
            )
        }
    }
    // 会話カラムを開く
    (status ?: summary?.last_status)?.let {
        conversation(pos, accessInfo, it)
    }
}

// プレビューカードのイメージは返信かもしれない
fun ActMain.clickCardImage(pos: Int, accessInfo: SavedAccount, card: TootCard?, longClick: Boolean = false) {
    card ?: return
    card.originalStatus?.let {
        if (longClick) {
            conversationOtherInstance(pos, it)
        } else {
            conversation(pos, accessInfo, it)
        }
        return
    }
    card.url?.notEmpty()?.let {
        openCustomTab(this, pos, it, accessInfo = accessInfo)
    }
}

// 「～からの返信」の表記を押した
fun ActMain.clickReplyInfo(
    pos: Int,
    accessInfo: SavedAccount,
    columnType: ColumnType,
    statusReply: TootStatus?,
    statusShowing: TootStatus?,
    longClick: Boolean = false,
    contextMenuOpener: ActMain.(status: TootStatus) -> Unit = {},
) {
    when {
        statusReply != null ->
            if (longClick) {
                contextMenuOpener(this, statusReply)
            } else {
                conversation(pos, accessInfo, statusReply)
            }

        // tootsearchは返信元のIDを取得するのにひと手間必要
        columnType == ColumnType.SEARCH_TS ||
            columnType == ColumnType.SEARCH_NOTESTOCK ->
            conversationFromTootsearch(pos, statusShowing)

        else ->
            statusShowing?.in_reply_to_id
                ?.let { conversationLocal(pos, accessInfo, it) }
    }
}

/////////////////////////////////////////////////////////////////////////////////////
// open conversation

// returns true if unread flag will be cleared.
internal fun ActMain.conversationUnreadClear(
    accessInfo: SavedAccount,
    conversationSummary: TootConversationSummary?,
): Boolean {

    // サマリがない
    conversationSummary ?: return false

    // 更新の必要がない
    if (!conversationSummary.unread) return false

    // 変数を更新
    conversationSummary.unread = false

    // 未読フラグのクリアをサーバに送る
    launchMain {
        runApiTask(accessInfo, progressStyle = ApiTask.PROGRESS_NONE) { client ->
            client.request(
                "/api/v1/conversations/${conversationSummary.id}/read",
                "".toFormRequestBody().toPost()
            )
        }
        // 応答の内容は見ない
    }

    return true // 表示も更新されるべき
}

// ローカルかリモートか判断する
fun ActMain.conversation(
    pos: Int,
    accessInfo: SavedAccount,
    status: TootStatus,
) {
    if (accessInfo.isNA || !accessInfo.matchHost(status.readerApDomain)) {
        conversationOtherInstance(pos, status)
    } else {

        conversationLocal(pos, accessInfo, status.id)
    }
}

// ローカルから見える会話の流れを表示する
fun ActMain.conversationLocal(
    pos: Int,
    accessInfo: SavedAccount,
    statusId: EntityId,
) = addColumn(pos, accessInfo, ColumnType.CONVERSATION, statusId)

private val reDetailedStatusTime =
    """<a\b[^>]*?\bdetailed-status__datetime\b[^>]*href="https://[^/]+/@[^/]+/([^\s?#/"]+)"""
        .toRegex()

private val reHeaderOgUrl = """<meta\s+content="https://[^/"]+/notice/([^/"]+)"\s+property="og:url"/?>"""
    .toRegex()

// 疑似アカウントではURLからIDを取得するのにHTMLと正規表現を使う
suspend fun guessStatusIdFromPseudoAccount(
    context: Context, // for string resource
    client: TootApiClient, // for get HTML
    remoteStatusUrl: String,
): Pair<TootApiResult?, EntityId?> {

    val result = client.getHttp(remoteStatusUrl)

    result?.string?.let { html ->

        reDetailedStatusTime.find(html)
            ?.groupValues?.elementAtOrNull(1)
            ?.let { return Pair(result, EntityId(it)) }

        reHeaderOgUrl.find(html)
            ?.groupValues?.elementAtOrNull(1)
            ?.let { return Pair(result, EntityId(it)) }
    }

    return Pair(
        result?.setError(context.getString(R.string.status_id_conversion_failed)),
        null
    )
}

private fun ActMain.conversationRemote(
    pos: Int,
    accessInfo: SavedAccount,
    remoteStatusUrl: String,
) {

    launchMain {
        var localStatusId: EntityId? = null

        runApiTask(
            accessInfo,
            progressPrefix = getString(R.string.progress_synchronize_toot)
        ) { client ->
            if (accessInfo.isPseudo) {
                // 疑似アカウントではURLからIDを取得するのにHTMLと正規表現を使う
                val pair = guessStatusIdFromPseudoAccount(applicationContext, client, remoteStatusUrl)
                localStatusId = pair.second
                pair.first
            } else {
                // 実アカウントでは検索APIを使える
                val (result, status) = client.syncStatus(accessInfo, remoteStatusUrl)
                if (status != null) {
                    localStatusId = status.id
                    log.d("status id conversion $remoteStatusUrl=>${status.id}")
                }
                result
            }
        }?.let { result ->
            when (val statusId = localStatusId) {
                null -> showToast(true, result.error)
                else -> conversationLocal(pos, accessInfo, statusId)
            }
        }
    }
}

// アプリ外部からURLを渡された場合に呼ばれる
fun ActMain.conversationOtherInstance(
    pos: Int,
    url: String,
    statusIdOriginal: EntityId? = null,
    hostAccess: Host? = null,
    statusIdAccess: EntityId? = null,
) {
    val activity = this

    val dialog = ActionsDialog()

    val hostOriginal = Host.parse(url.toUri().authority ?: "")

    // 選択肢：ブラウザで表示する
    dialog.addAction(getString(R.string.open_web_on_host, hostOriginal.pretty)) { openCustomTab(url) }

    // トゥートの投稿元タンスにあるアカウント
    val localAccountList = ArrayList<SavedAccount>()

    // TLを読んだタンスにあるアカウント
    val accessAccountList = ArrayList<SavedAccount>()

    // その他のタンスにあるアカウント
    val otherAccountList = ArrayList<SavedAccount>()

    for (a in SavedAccount.loadAccountList(applicationContext)) {

        // 疑似アカウントは後でまとめて処理する
        if (a.isPseudo) continue

        if (statusIdOriginal != null && a.matchHost(hostOriginal)) {
            // アクセス情報＋ステータスID でアクセスできるなら
            // 同タンスのアカウントならステータスIDの変換なしに表示できる
            localAccountList.add(a)
        } else if (statusIdAccess != null && a.matchHost(hostAccess)) {
            // 既に変換済みのステータスIDがあるなら、そのアカウントでもステータスIDの変換は必要ない
            accessAccountList.add(a)
        } else {
            // 別タンスでも実アカウントなら検索APIでステータスIDを変換できる
            otherAccountList.add(a)
        }
    }

    // 同タンスのアカウントがないなら、疑似アカウントで開く選択肢
    if (localAccountList.isEmpty()) {
        if (statusIdOriginal != null) {
            dialog.addAction(
                getString(R.string.open_in_pseudo_account, "?@${hostOriginal.pretty}")
            ) {
                launchMain {
                    addPseudoAccount(hostOriginal)?.let { sa ->
                        conversationLocal(pos, sa, statusIdOriginal)
                    }
                }
            }
        } else {
            dialog.addAction(
                getString(R.string.open_in_pseudo_account, "?@${hostOriginal.pretty}")
            ) {
                launchMain {
                    addPseudoAccount(hostOriginal)?.let { sa ->
                        conversationRemote(pos, sa, url)
                    }
                }
            }
        }
    }

    // ローカルアカウント
    if (statusIdOriginal != null) {
        SavedAccount.sort(localAccountList)
        for (a in localAccountList) {
            dialog.addAction(
                AcctColor.getStringWithNickname(
                    activity,
                    R.string.open_in_account,
                    a.acct
                )
            ) { conversationLocal(pos, a, statusIdOriginal) }
        }
    }

    // アクセスしたアカウント
    if (statusIdAccess != null) {
        SavedAccount.sort(accessAccountList)
        for (a in accessAccountList) {
            dialog.addAction(
                AcctColor.getStringWithNickname(
                    activity,
                    R.string.open_in_account,
                    a.acct
                )
            ) { conversationLocal(pos, a, statusIdAccess) }
        }
    }

    // その他の実アカウント
    SavedAccount.sort(otherAccountList)
    for (a in otherAccountList) {
        dialog.addAction(
            AcctColor.getStringWithNickname(
                activity,
                R.string.open_in_account,
                a.acct
            )
        ) { conversationRemote(pos, a, url) }
    }

    dialog.show(activity, activity.getString(R.string.open_status_from))
}

// リモートかもしれない会話の流れを表示する
fun ActMain.conversationOtherInstance(
    pos: Int,
    status: TootStatus?,
) {
    // URLが不明なトゥートというのはreblogの外側のアレ
    val url = status?.url?.notEmpty() ?: return

    when {

        // 検索サービスではステータスTLをどのタンスから読んだのか分からない
        status.readerApDomain == null ->
            conversationOtherInstance(
                pos,
                url,
                TootStatus.validStatusId(status.id)
                    ?: TootStatus.findStatusIdFromUri(
                        status.uri,
                        status.url
                    )
            )

        // TLアカウントのホストとトゥートのアカウントのホストが同じ
        status.originalApDomain == status.readerApDomain ->
            conversationOtherInstance(
                pos,
                url,
                TootStatus.validStatusId(status.id)
                    ?: TootStatus.findStatusIdFromUri(
                        status.uri,
                        status.url
                    )
            )

        else -> {
            // トゥートを取得したタンスと投稿元タンスが異なる場合
            // status.id はトゥートを取得したタンスでのIDである
            // 投稿元タンスでのIDはuriやURLから調べる
            // pleromaではIDがuuidなので失敗する(その時はURLを検索してIDを見つける)
            conversationOtherInstance(
                pos, url, TootStatus.findStatusIdFromUri(
                    status.uri,
                    status.url
                ), status.readerApDomain, TootStatus.validStatusId(status.id)
            )
        }
    }
}

////////////////////////////////////////

fun ActMain.conversationMute(
    accessInfo: SavedAccount,
    status: TootStatus?,
) {
    status ?: return

    // toggle change
    val bMute = !status.muted

    launchMain {
        var localStatus: TootStatus? = null
        runApiTask(accessInfo) { client ->
            client.request(
                "/api/v1/statuses/${status.id}/${if (bMute) "mute" else "unmute"}",
                "".toFormRequestBody().toPost()
            )?.also { result ->
                localStatus = TootParser(this, accessInfo).status(result.jsonObject)
            }
        }?.let { result ->
            when (val ls = localStatus) {
                null -> showToast(true, result.error)

                else -> {
                    for (column in appState.columnList) {
                        if (accessInfo == column.accessInfo) {
                            column.findStatus(accessInfo.apDomain, ls.id) { _, status ->
                                status.muted = bMute
                                true
                            }
                        }
                    }
                    showToast(
                        true,
                        if (bMute) R.string.mute_succeeded else R.string.unmute_succeeded
                    )
                }
            }
        }
    }
}

//////////////////////////////////////////////////

// tootsearch APIは投稿の返信元を示すreplyの情報がない。
// in_reply_to_idを参照するしかない
// ところがtootsearchでは投稿をどのタンスから読んだか分からないので、IDは全面的に信用できない。
// 疑似ではないアカウントを選んだ後に表示中の投稿を検索APIで調べて、そのリプライのIDを取得しなおす
fun ActMain.conversationFromTootsearch(
    pos: Int,
    statusArg: TootStatus?,
) {
    statusArg ?: return

    // step2: 選択したアカウントで投稿を検索して返信元の投稿のIDを調べる
    fun step2(a: SavedAccount) = launchMain {
        var tmp: TootStatus? = null
        runApiTask(a) { client ->
            val (result, status) = client.syncStatus(a, statusArg)
            tmp = status
            result
        }?.let { result ->
            val status = tmp
            val replyId = status?.in_reply_to_id
            when {
                status == null -> showToast(true, result.error ?: "?")
                replyId == null -> showToast(true, "showReplyTootsearch: in_reply_to_id is null")
                else -> conversationLocal(pos, a, replyId)
            }
        }
    }

    // step 1: choose account

    val host = statusArg.account.apDomain
    val localAccountList = ArrayList<SavedAccount>()
    val otherAccountList = ArrayList<SavedAccount>()

    for (a in SavedAccount.loadAccountList(this)) {

        // 検索APIはログイン必須なので疑似アカウントは使えない
        if (a.isPseudo) continue

        if (a.matchHost(host)) {
            localAccountList.add(a)
        } else {
            otherAccountList.add(a)
        }
    }

    val dialog = ActionsDialog()

    SavedAccount.sort(localAccountList)
    for (a in localAccountList) {
        dialog.addAction(
            AcctColor.getStringWithNickname(
                this,
                R.string.open_in_account,
                a.acct
            )
        ) { step2(a) }
    }

    SavedAccount.sort(otherAccountList)
    for (a in otherAccountList) {
        dialog.addAction(
            AcctColor.getStringWithNickname(
                this,
                R.string.open_in_account,
                a.acct
            )
        ) { step2(a) }
    }

    dialog.show(this, getString(R.string.open_status_from))
}
