/*
 * Copyright (c) 2012 Todoroo Inc
 *
 * See the file "LICENSE" for the full license governing this code.
 */
package com.todoroo.astrid.activity

import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.graphics.Paint
import android.os.Bundle
import android.text.format.DateUtils
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.Toolbar
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.layout.*
import androidx.compose.material.Divider
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidViewBinding
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.Behavior.DragCallback
import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.files.FilesControlSet
import com.todoroo.astrid.repeats.RepeatControlSet
import com.todoroo.astrid.tags.TagsControlSet
import com.todoroo.astrid.timers.TimerControlSet
import com.todoroo.astrid.timers.TimerPlugin
import com.todoroo.astrid.ui.ReminderControlSet
import com.todoroo.astrid.ui.StartDateControlSet
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.analytics.Firebase
import org.tasks.calendars.CalendarPicker
import org.tasks.compose.BeastModeBanner
import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.compose.edit.*
import org.tasks.data.Alarm
import org.tasks.data.Location
import org.tasks.data.TagData
import org.tasks.data.UserActivityDao
import org.tasks.databinding.*
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.dialogs.DateTimePicker
import org.tasks.dialogs.DialogBuilder
import org.tasks.dialogs.FilterPicker.Companion.newFilterPicker
import org.tasks.dialogs.FilterPicker.Companion.setFilterPickerResultListener
import org.tasks.dialogs.Linkify
import org.tasks.files.FileHelper
import org.tasks.fragments.TaskEditControlSetFragmentManager
import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_CREATION
import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_DESCRIPTION
import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_DUE_DATE
import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_LIST
import org.tasks.fragments.TaskEditControlSetFragmentManager.Companion.TAG_PRIORITY
import org.tasks.markdown.MarkdownProvider
import org.tasks.notifications.NotificationManager
import org.tasks.preferences.Preferences
import org.tasks.ui.*
import org.tasks.ui.TaskEditViewModel.Companion.stripCarriageReturns
import java.time.format.FormatStyle
import java.util.*
import javax.inject.Inject
import kotlin.math.abs
import android.view.inputmethod.EditorInfo

@AndroidEntryPoint
class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener {
    @Inject lateinit var taskDao: TaskDao
    @Inject lateinit var userActivityDao: UserActivityDao
    @Inject lateinit var notificationManager: NotificationManager
    @Inject lateinit var dialogBuilder: DialogBuilder
    @Inject lateinit var context: Activity
    @Inject lateinit var taskEditControlSetFragmentManager: TaskEditControlSetFragmentManager
    @Inject lateinit var preferences: Preferences
    @Inject lateinit var firebase: Firebase
    @Inject lateinit var timerPlugin: TimerPlugin
    @Inject lateinit var linkify: Linkify
    @Inject lateinit var markdownProvider: MarkdownProvider
    @Inject lateinit var taskEditEventBus: TaskEditEventBus
    @Inject lateinit var localBroadcastManager: LocalBroadcastManager
    @Inject lateinit var locale: Locale
    @Inject lateinit var chipProvider: ChipProvider

    val editViewModel: TaskEditViewModel by viewModels()
    val subtaskViewModel: TaskListViewModel by viewModels()
    lateinit var binding: FragmentTaskEditBinding
    private var showKeyboard = false
    private val refreshReceiver = RefreshReceiver()
    private val beastMode =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            activity?.recreate()
        }

    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = FragmentTaskEditBinding.inflate(inflater)
        val view: View = binding.root
        val model = editViewModel.task
        val toolbar = binding.toolbar
        toolbar.navigationIcon = AppCompatResources.getDrawable(
            context,
            if (editViewModel.isReadOnly)
                R.drawable.ic_outline_arrow_back_24px
            else
                R.drawable.ic_outline_save_24px
        )
        toolbar.setNavigationOnClickListener {
            lifecycleScope.launch {
                save()
            }
        }
        val backButtonSavesTask = preferences.backButtonSavesTask()
        toolbar.setNavigationContentDescription(
            when {
                editViewModel.isReadOnly -> R.string.back
                backButtonSavesTask -> R.string.discard
                else -> R.string.save
            }
        )
        toolbar.inflateMenu(R.menu.menu_task_edit_fragment)
        val menu = toolbar.menu
        val delete = menu.findItem(R.id.menu_delete)
        delete.isVisible = !model.isNew && editViewModel.isWritable
        delete.setShowAsAction(
                if (backButtonSavesTask) MenuItem.SHOW_AS_ACTION_NEVER else MenuItem.SHOW_AS_ACTION_IF_ROOM)
        val discard = menu.findItem(R.id.menu_discard)
        discard.isVisible = backButtonSavesTask && editViewModel.isWritable
        discard.setShowAsAction(
                if (model.isNew) MenuItem.SHOW_AS_ACTION_IF_ROOM else MenuItem.SHOW_AS_ACTION_NEVER)
        if (savedInstanceState == null) {
            showKeyboard = model.isNew && isNullOrEmpty(model.title)
        }
        val params = binding.appbarlayout.layoutParams as CoordinatorLayout.LayoutParams
        params.behavior = AppBarLayout.Behavior()
        val behavior = params.behavior as AppBarLayout.Behavior?
        behavior!!.setDragCallback(object : DragCallback() {
            override fun canDrag(appBarLayout: AppBarLayout): Boolean {
                return false
            }
        })
        toolbar.setOnMenuItemClickListener(this)
        val title = binding.title
        val textWatcher = markdownProvider.markdown(preferences.linkify).textWatcher(title)
        title.addTextChangedListener(
            onTextChanged = { _, _, _, _ ->
                editViewModel.title = title.text.toString().trim { it <= ' ' }
            },
            afterTextChanged = {
                textWatcher?.invoke(it)
            }
        )
        title.setOnEditorActionListener { _, actionId, _ ->
            if (actionId == EditorInfo.IME_ACTION_DONE) {
                lifecycleScope.launch {
                    save()
                }
                true
            } else false
        }
        title.setText(model.title)
        title.setHorizontallyScrolling(false)
        title.maxLines = 5
        if (editViewModel.isReadOnly) {
            title.isFocusable = false
            title.isFocusableInTouchMode = false
            title.isCursorVisible = false
        }
        if (
            model.isNew ||
            preferences.getBoolean(R.string.p_hide_check_button, false) ||
            editViewModel.isReadOnly
        ) {
            binding.fab.visibility = View.INVISIBLE
        } else if (editViewModel.completed) {
            title.paintFlags = title.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
            binding.fab.setImageResource(R.drawable.ic_outline_check_box_outline_blank_24px)
        }
        binding.fab.setOnClickListener {
            if (editViewModel.completed) {
                editViewModel.completed = false
                title.paintFlags = title.paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
                binding.fab.setImageResource(R.drawable.ic_outline_check_box_24px)
            } else {
                editViewModel.completed = true
                lifecycleScope.launch {
                    save()
                }
            }
        }
        binding.appbarlayout.addOnOffsetChangedListener { appBarLayout, verticalOffset ->
            if (verticalOffset == 0) {
                title.visibility = View.VISIBLE
                binding.collapsingtoolbarlayout.isTitleEnabled = false
            } else if (abs(verticalOffset) < appBarLayout.totalScrollRange) {
                title.visibility = View.INVISIBLE
                binding.collapsingtoolbarlayout.title = title.text
                binding.collapsingtoolbarlayout.isTitleEnabled = true
            }
        }
        if (!model.isNew) {
            lifecycleScope.launch {
                notificationManager.cancel(model.id)
            }
            if (preferences.linkify) {
                linkify.linkify(title)
            }
        }
        binding.composeView.setContent {
            MdcTheme {
                Column(modifier = Modifier.gesturesDisabled(editViewModel.isReadOnly)) {
                    taskEditControlSetFragmentManager.displayOrder.forEachIndexed { index, tag ->
                        if (index < taskEditControlSetFragmentManager.visibleSize) {
                            // TODO: remove ui-viewbinding library when these are all migrated
                            when (taskEditControlSetFragmentManager.controlSetFragments[tag]) {
                                TAG_DUE_DATE -> DueDateRow()
                                TAG_PRIORITY -> PriorityRow()
                                TAG_DESCRIPTION -> DescriptionRow()
                                TAG_LIST -> ListRow()
                                TAG_CREATION -> CreationRow()
                                CalendarControlSet.TAG -> AndroidViewBinding(TaskEditCalendarBinding::inflate)
                                StartDateControlSet.TAG -> AndroidViewBinding(
                                    TaskEditStartDateBinding::inflate
                                )
                                ReminderControlSet.TAG -> AndroidViewBinding(
                                    TaskEditRemindersBinding::inflate
                                )
                                LocationControlSet.TAG -> AndroidViewBinding(TaskEditLocationBinding::inflate)
                                FilesControlSet.TAG -> AndroidViewBinding(TaskEditFilesBinding::inflate)
                                TimerControlSet.TAG -> AndroidViewBinding(TaskEditTimerBinding::inflate)
                                TagsControlSet.TAG -> AndroidViewBinding(TaskEditTagsBinding::inflate)
                                RepeatControlSet.TAG -> AndroidViewBinding(TaskEditRepeatBinding::inflate)
                                SubtaskControlSet.TAG -> AndroidViewBinding(TaskEditSubtasksBinding::inflate)
                                else -> throw IllegalArgumentException("Unknown row: $tag")
                            }
                            Divider(modifier = Modifier.fillMaxWidth())
                        }
                    }
                    if (preferences.getBoolean(R.string.p_show_task_edit_comments, true)) {
                        Comments()
                    }
                }
            }
        }
        childFragmentManager.setFilterPickerResultListener(this) { filter ->
            editViewModel.selectedList.update { filter }
        }
        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        taskEditEventBus
            .onEach(this::process)
            .launchIn(viewLifecycleOwner.lifecycleScope)
    }

    private suspend fun process(event: TaskEditEvent) {
        when (event) {
            is TaskEditEvent.Discard ->
                if (event.id == editViewModel.task.id) {
                    editViewModel.discard()
                }
        }
    }

    @OptIn(ExperimentalAnimationApi::class)
    private fun showBeastModeHint() {
        binding.banner.setContent {
            var visible by rememberSaveable { mutableStateOf(true) }
            val context = LocalContext.current
            MdcTheme {
                BeastModeBanner(
                    visible,
                    showSettings = {
                        visible = false
                        preferences.shownBeastModeHint = true
                        beastMode.launch(Intent(context, BeastModePreferences::class.java))
                        firebase.logEvent(R.string.event_banner_beast, R.string.param_click to true)
                    },
                    dismiss = {
                        visible = false
                        preferences.shownBeastModeHint = true
                        firebase.logEvent(R.string.event_banner_beast, R.string.param_click to false)
                    }
                )
            }
        }
    }

    override fun onResume() {
        super.onResume()
        localBroadcastManager.registerRefreshReceiver(refreshReceiver)
        if (showKeyboard) {
            binding.title.requestFocus()
            val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
            imm.showSoftInput(binding.title, InputMethodManager.SHOW_IMPLICIT)
        }
        if (!preferences.shownBeastModeHint) {
            showBeastModeHint()
        }
    }

    override fun onPause() {
        super.onPause()
        localBroadcastManager.unregisterReceiver(refreshReceiver)
    }

    override fun onMenuItemClick(item: MenuItem): Boolean {
        AndroidUtilities.hideKeyboard(activity)
        if (item.itemId == R.id.menu_delete) {
            deleteButtonClick()
            return true
        } else if (item.itemId == R.id.menu_discard) {
            discardButtonClick()
            return true
        }
        return false
    }

    suspend fun stopTimer(): Task {
        val model = editViewModel.task
        timerPlugin.stopTimer(model)
        val elapsedTime = DateUtils.formatElapsedTime(model.elapsedSeconds.toLong())
        editViewModel.addComment(String.format(
                "%s %s\n%s %s",  // $NON-NLS-1$
                getString(R.string.TEA_timer_comment_stopped),
                DateUtilities.getTimeString(context, newDateTime()),
                getString(R.string.TEA_timer_comment_spent),
                elapsedTime),
                null)
        return model
    }

    suspend fun startTimer(): Task {
        val model = editViewModel.task
        timerPlugin.startTimer(model)
        editViewModel.addComment(String.format(
                "%s %s",
                getString(R.string.TEA_timer_comment_started),
                DateUtilities.getTimeString(context, newDateTime())),
                null)
        return model
    }

    suspend fun save(remove: Boolean = true) = editViewModel.save(remove)

    fun discardButtonClick() {
       if (editViewModel.hasChanges()) {
           dialogBuilder
                   .newDialog(R.string.discard_confirmation)
                   .setPositiveButton(R.string.keep_editing, null)
                   .setNegativeButton(R.string.discard) { _, _ -> discard() }
                   .show()
       } else {
           discard()
       }
    }

    private fun deleteButtonClick() {
        dialogBuilder
                .newDialog(R.string.DLG_delete_this_task_question)
                .setPositiveButton(R.string.ok) { _, _ -> delete() }
                .setNegativeButton(R.string.cancel, null)
                .show()
    }

    private fun discard() = lifecycleScope.launch {
        editViewModel.discard()
    }

    private fun delete() = lifecycleScope.launch {
        editViewModel.delete()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        when (requestCode) {
            REQUEST_DATE -> {
                if (resultCode == Activity.RESULT_OK) {
                    editViewModel.setDueDate(data!!.getLongExtra(DateTimePicker.EXTRA_TIMESTAMP, 0L))
                }
            }
            REQUEST_CODE_PICK_CALENDAR -> {
                if (resultCode == Activity.RESULT_OK) {
                    editViewModel.selectedCalendar.value =
                        data!!.getStringExtra(CalendarPicker.EXTRA_CALENDAR_ID)
                }
            }
            else -> super.onActivityResult(requestCode, resultCode, data)
        }
    }

    private inner class RefreshReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            subtaskViewModel.invalidate()
        }
    }

    @Composable
    private fun DueDateRow() {
        val dueDate = editViewModel.dueDate.collectAsStateLifecycleAware().value
        DueDateRow(
            dueDate = if (dueDate == 0L) {
                null
            } else {
                DateUtilities.getRelativeDateTime(
                    LocalContext.current,
                    dueDate,
                    locale,
                    FormatStyle.FULL,
                    preferences.alwaysDisplayFullDate,
                    false
                )
            },
            overdue = dueDate.isOverdue,
            onClick = {
                DateTimePicker
                    .newDateTimePicker(
                        this@TaskEditFragment,
                        REQUEST_DATE,
                        editViewModel.dueDate.value,
                        preferences.getBoolean(
                            R.string.p_auto_dismiss_datetime_edit_screen,
                            false
                        )
                    )
                    .show(parentFragmentManager, FRAG_TAG_DATE_PICKER)
            }
        )
    }

    @Composable
    private fun PriorityRow() {
        PriorityRow(
            priority = editViewModel.priority.collectAsStateLifecycleAware().value,
            onChangePriority = { editViewModel.priority.value = it },
            desaturate = preferences.desaturateDarkMode,
        )
    }

    @Composable
    private fun DescriptionRow() {
        DescriptionRow(
            text = editViewModel.description.stripCarriageReturns(),
            onChanged = { text -> editViewModel.description = text.toString().trim { it <= ' ' } },
            linkify = if (preferences.linkify) linkify else null,
            markdownProvider = markdownProvider,
        )
    }

    @Composable
    private fun ListRow() {
        val list = editViewModel.selectedList.collectAsStateLifecycleAware().value
        ListRow(
            list = list,
            colorProvider = { chipProvider.getColor(it) },
            onClick = {
                newFilterPicker(list, true)
                    .show(
                        childFragmentManager,
                        FRAG_TAG_GOOGLE_TASK_LIST_SELECTION
                    )
            }
        )
    }

    @Composable
    fun CreationRow() {
        InfoRow(
            creationDate = editViewModel.creationDate,
            modificationDate = editViewModel.modificationDate,
            completionDate = editViewModel.completionDate,
            locale = locale,
        )
    }

    @Composable
    fun Comments() {
        CommentsRow(
            comments = userActivityDao
                .watchComments(editViewModel.task.uuid)
                .collectAsStateLifecycleAware(emptyList())
                .value,
            deleteComment = {
                lifecycleScope.launch {
                    userActivityDao.delete(it)
                }
            },
            openImage = { FileHelper.startActionView(requireActivity(), it) }
        )
    }

    companion object {
        const val TAG_TASKEDIT_FRAGMENT = "taskedit_fragment"
        const val EXTRA_TASK = "extra_task"
        const val EXTRA_LIST = "extra_list"
        const val EXTRA_LOCATION = "extra_location"
        const val EXTRA_TAGS = "extra_tags"
        const val EXTRA_ALARMS = "extra_alarms"

        private const val FRAG_TAG_GOOGLE_TASK_LIST_SELECTION =
            "frag_tag_google_task_list_selection"
        const val FRAG_TAG_CALENDAR_PICKER = "frag_tag_calendar_picker"
        private const val FRAG_TAG_DATE_PICKER = "frag_tag_date_picker"
        const val REQUEST_CODE_PICK_CALENDAR = 70
        private const val REQUEST_DATE = 504

        val Long.isOverdue: Boolean
            get() = if (Task.hasDueTime(this)) {
                newDateTime(this).isBeforeNow
            } else {
                newDateTime(this).endOfDay().isBeforeNow
            }

        fun newTaskEditFragment(
            task: Task,
            list: Filter,
            location: Location?,
            tags: ArrayList<TagData>,
            alarms: ArrayList<Alarm>,
        ): TaskEditFragment {
            val taskEditFragment = TaskEditFragment()
            val arguments = Bundle()
            arguments.putParcelable(EXTRA_TASK, task)
            arguments.putParcelable(EXTRA_LIST, list)
            arguments.putParcelable(EXTRA_LOCATION, location)
            arguments.putParcelableArrayList(EXTRA_TAGS, tags)
            arguments.putParcelableArrayList(EXTRA_ALARMS, alarms)
            taskEditFragment.arguments = arguments
            return taskEditFragment
        }

        fun Modifier.gesturesDisabled(disabled: Boolean = true) =
            if (disabled) {
                pointerInput(Unit) {
                    awaitPointerEventScope {
                        while (true) {
                            awaitPointerEvent(pass = PointerEventPass.Initial)
                                .changes
                                .filter { it.position == it.previousPosition }
                                .forEach { it.consume() }
                        }
                    }
                }
            } else {
                this
            }
    }
}