package com.keylesspalace.tusky.components.filters

import android.content.Context
import android.content.DialogInterface.BUTTON_POSITIVE
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.core.content.IntentCompat
import androidx.core.view.size
import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope
import at.connyduck.calladapter.networkresult.fold
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.switchmaterial.SwitchMaterial
import com.keylesspalace.tusky.BaseActivity
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.appstore.EventHub
import com.keylesspalace.tusky.appstore.FilterUpdatedEvent
import com.keylesspalace.tusky.databinding.ActivityEditFilterBinding
import com.keylesspalace.tusky.databinding.DialogFilterBinding
import com.keylesspalace.tusky.di.ViewModelFactory
import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.FilterKeyword
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.isHttpNotFound
import com.keylesspalace.tusky.util.viewBinding
import com.keylesspalace.tusky.util.visible
import java.util.Date
import javax.inject.Inject
import kotlinx.coroutines.launch

class EditFilterActivity : BaseActivity() {
    @Inject
    lateinit var api: MastodonApi

    @Inject
    lateinit var eventHub: EventHub

    @Inject
    lateinit var viewModelFactory: ViewModelFactory

    private val binding by viewBinding(ActivityEditFilterBinding::inflate)
    private val viewModel: EditFilterViewModel by viewModels { viewModelFactory }

    private lateinit var filter: Filter
    private var originalFilter: Filter? = null
    private lateinit var contextSwitches: Map<SwitchMaterial, Filter.Kind>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        originalFilter = IntentCompat.getParcelableExtra(intent, FILTER_TO_EDIT, Filter::class.java)
        filter = originalFilter ?: Filter("", "", listOf(), null, Filter.Action.WARN.action, listOf())
        binding.apply {
            contextSwitches = mapOf(
                filterContextHome to Filter.Kind.HOME,
                filterContextNotifications to Filter.Kind.NOTIFICATIONS,
                filterContextPublic to Filter.Kind.PUBLIC,
                filterContextThread to Filter.Kind.THREAD,
                filterContextAccount to Filter.Kind.ACCOUNT
            )
        }

        setContentView(binding.root)
        setSupportActionBar(binding.includedToolbar.toolbar)
        supportActionBar?.run {
            // Back button
            setDisplayHomeAsUpEnabled(true)
            setDisplayShowHomeEnabled(true)
        }

        setTitle(
            if (originalFilter == null) {
                R.string.filter_addition_title
            } else {
                R.string.filter_edit_title
            }
        )

        binding.actionChip.setOnClickListener { showAddKeywordDialog() }
        binding.filterSaveButton.setOnClickListener { saveChanges() }
        binding.filterDeleteButton.setOnClickListener {
            lifecycleScope.launch {
                if (showDeleteFilterDialog(filter.title) == BUTTON_POSITIVE) deleteFilter()
            }
        }
        binding.filterDeleteButton.visible(originalFilter != null)

        for (switch in contextSwitches.keys) {
            switch.setOnCheckedChangeListener { _, isChecked ->
                val context = contextSwitches[switch]!!
                if (isChecked) {
                    viewModel.addContext(context)
                } else {
                    viewModel.removeContext(context)
                }
                validateSaveButton()
            }
        }
        binding.filterTitle.doAfterTextChanged { editable ->
            viewModel.setTitle(editable.toString())
            validateSaveButton()
        }
        binding.filterActionWarn.setOnCheckedChangeListener { _, checked ->
            viewModel.setAction(
                if (checked) {
                    Filter.Action.WARN
                } else {
                    Filter.Action.HIDE
                }
            )
        }
        binding.filterDurationSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(
                parent: AdapterView<*>?,
                view: View?,
                position: Int,
                id: Long
            ) {
                viewModel.setDuration(
                    if (originalFilter?.expiresAt == null) {
                        position
                    } else {
                        position - 1
                    }
                )
            }

            override fun onNothingSelected(parent: AdapterView<*>?) {
                viewModel.setDuration(0)
            }
        }
        validateSaveButton()

        if (originalFilter == null) {
            binding.filterActionWarn.isChecked = true
        } else {
            loadFilter()
        }
        observeModel()
    }

    private fun observeModel() {
        lifecycleScope.launch {
            viewModel.title.collect { title ->
                if (title != binding.filterTitle.text.toString()) {
                    // We also get this callback when typing in the field,
                    // which messes with the cursor focus
                    binding.filterTitle.setText(title)
                }
            }
        }
        lifecycleScope.launch {
            viewModel.keywords.collect { keywords ->
                updateKeywords(keywords)
            }
        }
        lifecycleScope.launch {
            viewModel.contexts.collect { contexts ->
                for (entry in contextSwitches) {
                    entry.key.isChecked = contexts.contains(entry.value)
                }
            }
        }
        lifecycleScope.launch {
            viewModel.action.collect { action ->
                when (action) {
                    Filter.Action.HIDE -> binding.filterActionHide.isChecked = true
                    else -> binding.filterActionWarn.isChecked = true
                }
            }
        }
    }

    // Populate the UI from the filter's members
    private fun loadFilter() {
        viewModel.load(filter)
        if (filter.expiresAt != null) {
            val durationNames = listOf(getString(R.string.duration_no_change)) + resources.getStringArray(R.array.filter_duration_names)
            binding.filterDurationSpinner.adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, durationNames)
        }
    }

    private fun updateKeywords(newKeywords: List<FilterKeyword>) {
        newKeywords.forEachIndexed { index, filterKeyword ->
            val chip = binding.keywordChips.getChildAt(index).takeUnless {
                it.id == R.id.actionChip
            } as Chip? ?: Chip(this).apply {
                setCloseIconResource(R.drawable.ic_cancel_24dp)
                isCheckable = false
                binding.keywordChips.addView(this, binding.keywordChips.size - 1)
            }

            chip.text = if (filterKeyword.wholeWord) {
                binding.root.context.getString(
                    R.string.filter_keyword_display_format,
                    filterKeyword.keyword
                )
            } else {
                filterKeyword.keyword
            }
            chip.isCloseIconVisible = true
            chip.setOnClickListener {
                showEditKeywordDialog(newKeywords[index])
            }
            chip.setOnCloseIconClickListener {
                viewModel.deleteKeyword(newKeywords[index])
            }
        }

        while (binding.keywordChips.size - 1 > newKeywords.size) {
            binding.keywordChips.removeViewAt(newKeywords.size)
        }

        filter = filter.copy(keywords = newKeywords)
        validateSaveButton()
    }

    private fun showAddKeywordDialog() {
        val binding = DialogFilterBinding.inflate(layoutInflater)
        binding.phraseWholeWord.isChecked = true
        AlertDialog.Builder(this)
            .setTitle(R.string.filter_keyword_addition_title)
            .setView(binding.root)
            .setPositiveButton(android.R.string.ok) { _, _ ->
                viewModel.addKeyword(
                    FilterKeyword(
                        "",
                        binding.phraseEditText.text.toString(),
                        binding.phraseWholeWord.isChecked
                    )
                )
            }
            .setNegativeButton(android.R.string.cancel, null)
            .show()
    }

    private fun showEditKeywordDialog(keyword: FilterKeyword) {
        val binding = DialogFilterBinding.inflate(layoutInflater)
        binding.phraseEditText.setText(keyword.keyword)
        binding.phraseWholeWord.isChecked = keyword.wholeWord

        AlertDialog.Builder(this)
            .setTitle(R.string.filter_edit_keyword_title)
            .setView(binding.root)
            .setPositiveButton(R.string.filter_dialog_update_button) { _, _ ->
                viewModel.modifyKeyword(
                    keyword,
                    keyword.copy(
                        keyword = binding.phraseEditText.text.toString(),
                        wholeWord = binding.phraseWholeWord.isChecked
                    )
                )
            }
            .setNegativeButton(android.R.string.cancel, null)
            .show()
    }

    private fun validateSaveButton() {
        binding.filterSaveButton.isEnabled = viewModel.validate()
    }

    private fun saveChanges() {
        // TODO use a progress bar here (see EditProfileActivity/activity_edit_profile.xml for example)?

        lifecycleScope.launch {
            if (viewModel.saveChanges(this@EditFilterActivity)) {
                finish()
                // Possibly affected contexts: any context affected by the original filter OR any context affected by the updated filter
                val affectedContexts = viewModel.contexts.value.map {
                    it.kind
                }.union(originalFilter?.context ?: listOf()).distinct()
                eventHub.dispatch(FilterUpdatedEvent(affectedContexts))
            } else {
                Snackbar.make(
                    binding.root,
                    "Error saving filter '${viewModel.title.value}'",
                    Snackbar.LENGTH_SHORT
                ).show()
            }
        }
    }

    private fun deleteFilter() {
        originalFilter?.let { filter ->
            lifecycleScope.launch {
                api.deleteFilter(filter.id).fold(
                    {
                        finish()
                    },
                    { throwable ->
                        if (throwable.isHttpNotFound()) {
                            api.deleteFilterV1(filter.id).fold(
                                {
                                    finish()
                                },
                                {
                                    Snackbar.make(
                                        binding.root,
                                        "Error deleting filter '${filter.title}'",
                                        Snackbar.LENGTH_SHORT
                                    ).show()
                                }
                            )
                        } else {
                            Snackbar.make(
                                binding.root,
                                "Error deleting filter '${filter.title}'",
                                Snackbar.LENGTH_SHORT
                            ).show()
                        }
                    }
                )
            }
        }
    }

    companion object {
        const val FILTER_TO_EDIT = "FilterToEdit"

        // Mastodon *stores* the absolute date in the filter,
        // but create/edit take a number of seconds (relative to the time the operation is posted)
        fun getSecondsForDurationIndex(index: Int, context: Context?, default: Date? = null): Int? {
            return when (index) {
                -1 -> if (default == null) {
                    default
                } else {
                    ((default.time - System.currentTimeMillis()) / 1000).toInt()
                }
                0 -> null
                else -> context?.resources?.getIntArray(R.array.filter_duration_values)?.get(index)
            }
        }
    }
}
