package jp.juggler.subwaytooter

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.ArrayAdapter
import android.widget.CheckBox
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children
import jp.juggler.subwaytooter.api.ApiPath
import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.auth.AuthRepo
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.api.runApiTask
import jp.juggler.subwaytooter.column.ColumnType
import jp.juggler.subwaytooter.databinding.ActKeywordFilterBinding
import jp.juggler.subwaytooter.databinding.LvKeywordFilterBinding
import jp.juggler.subwaytooter.table.SavedAccount
import jp.juggler.subwaytooter.table.daoAcctColor
import jp.juggler.subwaytooter.table.daoSavedAccount
import jp.juggler.util.backPressed
import jp.juggler.util.coroutine.launchAndShowError
import jp.juggler.util.coroutine.launchMain
import jp.juggler.util.data.*
import jp.juggler.util.int
import jp.juggler.util.log.LogCategory
import jp.juggler.util.log.showToast
import jp.juggler.util.long
import jp.juggler.util.network.toPostRequestBuilder
import jp.juggler.util.network.toPut
import jp.juggler.util.network.toRequestBody
import jp.juggler.util.string
import jp.juggler.util.ui.setNavigationBack

class ActKeywordFilter : AppCompatActivity() {

    companion object {

        private val log = LogCategory("ActKeywordFilter")

        private const val EXTRA_ACCOUNT_DB_ID = "account_db_id"
        private const val EXTRA_FILTER_ID = "filter_id"
        private const val EXTRA_INITIAL_PHRASE = "initial_phrase"

        private const val STATE_EXPIRE_SPINNER = "expire_spinner"
        private const val STATE_EXPIRE_AT = "expire_at"
        private const val STATE_KEYWORDS = "keywords"
        private const val STATE_DELETE_IDS = "deleteIds"

        fun open(
            activity: Activity,
            ai: SavedAccount,
            filterId: EntityId? = null,
            initialPhrase: String? = null,
        ) {
            val intent = Intent(activity, ActKeywordFilter::class.java)
            intent.putExtra(EXTRA_ACCOUNT_DB_ID, ai.db_id)
            filterId?.putTo(intent, EXTRA_FILTER_ID)
            initialPhrase?.notEmpty()?.let { intent.putExtra(EXTRA_INITIAL_PHRASE, it) }
            activity.startActivity(intent)
        }

        private val expire_duration_list = intArrayOf(
            -1, // dont change
            0, // unlimited
            1800,
            3600,
            3600 * 6,
            3600 * 12,
            86400,
            86400 * 7
        )
    }

    private lateinit var account: SavedAccount

    private val views by lazy {
        ActKeywordFilterBinding.inflate(layoutInflater)
    }

//    private lateinit var tvAccount: TextView
//    private lateinit var etPhrase: EditText
//    private lateinit var cbContextHome: CheckBox
//    private lateinit var cbContextNotification: CheckBox
//    private lateinit var cbContextPublic: CheckBox
//    private lateinit var cbContextThread: CheckBox
//    private lateinit var cbContextProfile: CheckBox
//
//    private lateinit var cbFilterIrreversible: CheckBox
//    private lateinit var cbFilterWordMatch: CheckBox
//    private lateinit var tvExpire: TextView
//    private lateinit var spExpire: Spinner

    private var loading = false
    private var density: Float = 1f
    private var filterId: EntityId? = null
    private var filterExpire: Long = 0L

    private val deleteIds = HashSet<String>()

    val authRepo by lazy {
        AuthRepo(this)
    }

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

    override fun onCreate(savedInstanceState: Bundle?) {
        backPressed { confirmBack() }
        super.onCreate(savedInstanceState)
        App1.setActivityTheme(this)
        initUI()

        launchAndShowError {

            // filter ID の有無はUIに影響するのでinitUIより先に初期化する
            filterId = EntityId.entityId(intent, EXTRA_FILTER_ID)

            val a = intent.long(EXTRA_ACCOUNT_DB_ID)
                ?.let { daoSavedAccount.loadAccount(it) }
            if (a == null) {
                finish()
                return@launchAndShowError
            }
            account = a

            showAccount()

            if (savedInstanceState == null) {
                if (filterId != null) {
                    startLoading()
                } else {
                    views.spExpire.setSelection(1)
                    val initialText = intent.string(EXTRA_INITIAL_PHRASE)?.trim() ?: ""
                    views.etTitle.setText(initialText)
                    addKeywordArea(TootFilterKeyword(keyword = initialText))
                }
            } else {

                savedInstanceState.getStringArrayList(STATE_DELETE_IDS)
                    ?.let { deleteIds.addAll(it) }

                savedInstanceState.getStringArrayList(STATE_KEYWORDS)
                    ?.mapNotNull { it?.decodeJsonObject() }
                    ?.forEach {
                        try {
                            addKeywordArea(TootFilterKeyword(it))
                        } catch (ex: Throwable) {
                            log.e(ex, "can't decode TootFilterKeyword")
                        }
                    }

                savedInstanceState.int(STATE_EXPIRE_SPINNER)
                    ?.let { views.spExpire.setSelection(it) }

                savedInstanceState.long(STATE_EXPIRE_AT)
                    ?.let { filterExpire = it }
            }
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        if (!loading) {
            outState.putInt(STATE_EXPIRE_SPINNER, views.spExpire.selectedItemPosition)
            outState.putLong(STATE_EXPIRE_AT, filterExpire)

            outState.putStringArrayList(STATE_DELETE_IDS, ArrayList<String>(deleteIds))

            views.llKeywords.children
                .mapNotNull { (it.tag as? VhKeyword)?.encodeJson()?.toString() }
                .toList()
                .let { outState.putStringArrayList(STATE_KEYWORDS, ArrayList<String>(it)) }
        }
    }

    private fun initUI() {
        setContentView(views.root)
        setSupportActionBar(views.toolbar)
        setNavigationBack(views.toolbar)
        fixHorizontalMargin(views.llContent)

        this.density = resources.displayMetrics.density

        title = getString(
            when (filterId) {
                null -> R.string.keyword_filter_new
                else -> R.string.keyword_filter_edit
            }
        )

        views.btnSave.setOnClickListener { save() }
        views.btnAddKeyword.setOnClickListener {
            val ti = TootInstance.getCached(account)
            when {
                ti == null ->
                    showToast(true, "can't get server information")
                !ti.versionGE(TootInstance.VERSION_4_0_0) && views.llKeywords.childCount >= 1 ->
                    showToast(true, "before mastodon 4.0, allowed 1 keyword per 1 filter.")
                else -> addKeywordArea(TootFilterKeyword(keyword = ""))
            }
        }

        val captionList = arrayOf(
            getString(R.string.dont_change),
            getString(R.string.filter_expire_unlimited),
            getString(R.string.filter_expire_30min),
            getString(R.string.filter_expire_1hour),
            getString(R.string.filter_expire_6hour),
            getString(R.string.filter_expire_12hour),
            getString(R.string.filter_expire_1day),
            getString(R.string.filter_expire_1week)
        )
        val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, captionList)
        adapter.setDropDownViewResource(R.layout.lv_spinner_dropdown)
        views.spExpire.adapter = adapter
    }

    private fun confirmBack() {
        AlertDialog.Builder(this)
            .setMessage(R.string.keyword_filter_quit_waring)
            .setPositiveButton(R.string.ok) { _, _ -> finish() }
            .setNegativeButton(R.string.cancel, null)
            .show()
    }

    private fun showAccount() {
        views.tvAccount.text = daoAcctColor.getNicknameWithColor(account.acct)
    }

    private fun startLoading() {
        loading = true
        launchMain {
            var resultFilter: TootFilter? = null
            runApiTask(account) { client ->

                // try v2
                var result = client.request("${ApiPath.PATH_FILTERS_V2}/$filterId")
                result?.jsonObject?.let {
                    try {
                        resultFilter = TootFilter(it)
                        return@runApiTask result
                    } catch (ex: Throwable) {
                        log.e(ex, "parse error.")
                    }
                }

                if (result?.response?.code == 404) {
                    // try v1
                    result = client.request("${ApiPath.PATH_FILTERS_V1}/$filterId")
                    result?.jsonObject?.let {
                        try {
                            resultFilter = TootFilter(it)
                            return@runApiTask result
                        } catch (ex: Throwable) {
                            log.e(ex, "parse error.")
                        }
                    }
                }

                result
            }?.let { result ->
                loading = false
                when (val filter = resultFilter) {
                    null -> {
                        showToast(true, result.error ?: "?")
                        finish()
                    }
                    else -> onLoadComplete(filter)
                }
            }
            // キャンセル時はloadingはtrueのまま
        }
    }

    private fun onLoadComplete(filter: TootFilter) {
        loading = false

        filterExpire = filter.time_expires_at

        setContextChecked(filter, views.cbContextHome, TootFilterContext.Home)
        setContextChecked(filter, views.cbContextNotification, TootFilterContext.Notifications)
        setContextChecked(filter, views.cbContextPublic, TootFilterContext.Public)
        setContextChecked(filter, views.cbContextThread, TootFilterContext.Thread)
        setContextChecked(filter, views.cbContextProfile, TootFilterContext.Account)

        views.rgAction.check(if (filter.hide) views.rbHide.id else views.rbWarn.id)

        if (filter.keywords.isEmpty()) {
            filter.keywords = listOf(TootFilterKeyword(keyword = ""))
        }

        filter.keywords.forEach { addKeywordArea(it) }

        views.etTitle.setText(
            filter.title.notEmpty() ?: filter.keywords.firstOrNull()?.keyword ?: ""
        )

        views.tvExpire.text = if (filter.time_expires_at == 0L) {
            getString(R.string.filter_expire_unlimited)
        } else {
            TootStatus.formatTime(this, filter.time_expires_at, false)
        }
    }

    private fun setContextChecked(filter: TootFilter, cb: CheckBox, fc: TootFilterContext) {
        cb.isChecked = filter.hasContext(fc)
    }

    private fun save() {
        if (loading) return

        val vhList = views.llKeywords.children.mapNotNull { it.tag as? VhKeyword }.toList()
        if (vhList.isEmpty() || vhList.any { it.keyword.isEmpty() }) {
            showToast(true, R.string.filter_keyword_empty)
            return
        }

        val title = views.etTitle.text.toString().trim()
        if (title.isEmpty()) {
            showToast(true, R.string.filter_title_empty)
            return
        }

        launchMain {

            var result = saveV2(vhList, title)
            if (result?.response?.code == 404) {
                result = saveV1(vhList)
            }
            result ?: return@launchMain // cancelled

            val error = result.error
            if (error != null) {
                showToast(true, result.error)
            } else {
                val appState = App1.prepare(applicationContext, "ActKeywordFilter.save()")
                for (column in appState.columnList) {
                    if (column.type == ColumnType.KEYWORD_FILTER && column.accessInfo == account) {
                        column.filterReloadRequired = true
                    }
                }
                finish()
            }
        }
    }

    private fun filterParamBase() = buildJsonObject {
        fun JsonArray.putContextChecked(cb: CheckBox, fc: TootFilterContext) {
            if (cb.isChecked) add(fc.apiName)
        }

        put("context", JsonArray().apply {
            putContextChecked(views.cbContextHome, TootFilterContext.Home)
            putContextChecked(views.cbContextNotification, TootFilterContext.Notifications)
            putContextChecked(views.cbContextPublic, TootFilterContext.Public)
            putContextChecked(views.cbContextThread, TootFilterContext.Thread)
            putContextChecked(views.cbContextProfile, TootFilterContext.Account)
        })

        when (val seconds = expire_duration_list
            .elementAtOrNull(views.spExpire.selectedItemPosition)
            ?: -1
        ) {
            // dont change
            -1 -> Unit

            // unlimited
            0 -> when {
                // already unlimited. don't change.
                filterExpire <= 0L -> Unit
                // XXX: currently there is no way to remove expires from existing filter.
                else -> put("expires_in", Int.MAX_VALUE)
            }

            // set seconds
            else -> put("expires_in", seconds)
        }
    }

    private suspend fun saveV1(vhList: List<VhKeyword>): TootApiResult? {
        if (vhList.size != 1) return TootApiResult("V1 API allow only 1 keyword.")

        val params = filterParamBase().apply {
            put("irreversible", views.rgAction.checkedRadioButtonId == views.rbHide.id)
            val vh = vhList.first()
            put("phrase", vh.keyword)
            put("whole_word", vh.wholeWord)
        }

        return runApiTask(account) { client ->
            if (filterId == null) {
                client.request(
                    ApiPath.PATH_FILTERS_V1,
                    params.toPostRequestBuilder()
                )
            } else {
                client.request(
                    "${ApiPath.PATH_FILTERS_V1}/$filterId",
                    params.toRequestBody().toPut()
                )
            }
        }
    }

    private suspend fun saveV2(vhList: List<VhKeyword>, title: String): TootApiResult? {
        val params = filterParamBase().apply {
            put("title", title)
            put(
                "filter_action",
                if (views.rbHide.isChecked) "hide" else "warn"
            )
            put("keywords_attributes", buildJsonArray {
                vhList.forEach { vh ->
                    add(buildJsonObject {
                        put("keyword", vh.keyword)
                        put("whole_word", vh.wholeWord)
                        vh.id?.let { put("id", it) }
                    })
                }
                deleteIds.forEach { id ->
                    add(buildJsonObject {
                        put("id", id)
                        put("_destroy", id)
                    })
                }
            })
        }
        return runApiTask(account) { client ->
            if (filterId == null) {
                client.request(
                    ApiPath.PATH_FILTERS_V2,
                    params.toPostRequestBuilder()
                )
            } else {
                client.request(
                    "${ApiPath.PATH_FILTERS_V2}/$filterId",
                    params.toRequestBody().toPut()
                )
            }
        }
    }

    private fun addKeywordArea(keyword: TootFilterKeyword) {
        views.llKeywords.addView(VhKeyword(fk = keyword).views.root)
    }

    private fun deleteKeywordArea(vh: VhKeyword) {
        views.llKeywords.children.find { it.tag == vh }
            ?.let { views.llKeywords.removeView(it) }
        vh.id?.let { deleteIds.add(it) }
    }

    private inner class VhKeyword(
        val fk: TootFilterKeyword,
        val views: LvKeywordFilterBinding = LvKeywordFilterBinding.inflate(layoutInflater),
    ) {
        init {
            views.root.tag = this
            views.etKeyword.setText(fk.keyword.trim())
            views.cbFilterWordMatch.isChecked = fk.whole_word

            views.btnDelete.setOnClickListener {
                deleteKeywordArea(this)
            }
        }

        // onSaveInstanceや保存時に呼ばれる
        fun encodeJson() =
            fk.encodeNewParam(newKeyword = keyword, newWholeWord = wholeWord)

        val keyword: String
            get() = views.etKeyword.text.toString().trim()

        val wholeWord: Boolean
            get() = views.cbFilterWordMatch.isChecked

        val id: String?
            get() = fk.id?.toString()?.notEmpty()
    }
}
