/*
 * Copyright 2019 Jeremy Jamet / Kunzisoft.
 *
 * This file is part of KeePassDX.
 *
 *  KeePassDX is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  KeePassDX is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with KeePassDX.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package com.kunzisoft.keepass.activities.fragments

import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.ReplaceFileDialogFragment
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
import com.kunzisoft.keepass.adapters.TagsProposalAdapter
import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.FieldProtection
import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.otp.OtpEntryFields.isOTP
import com.kunzisoft.keepass.utils.getParcelableList
import com.kunzisoft.keepass.utils.putParcelableList
import com.kunzisoft.keepass.view.TagsCompletionView
import com.kunzisoft.keepass.view.TemplateEditView
import com.kunzisoft.keepass.view.collapse
import com.kunzisoft.keepass.view.expand
import com.kunzisoft.keepass.view.showByFading
import com.kunzisoft.keepass.viewmodels.EntryEditViewModel
import com.tokenautocomplete.FilteredArrayAdapter
import kotlinx.coroutines.launch


class EntryEditFragment: DatabaseFragment() {

    private val mEntryEditViewModel: EntryEditViewModel by activityViewModels()

    private lateinit var rootView: View
    private lateinit var templateView: TemplateEditView
    private lateinit var attachmentsContainerView: ViewGroup
    private lateinit var attachmentsListView: RecyclerView
    private var attachmentsAdapter: EntryAttachmentsItemsAdapter? = null
    private lateinit var tagsContainerView: TextInputLayout
    private lateinit var tagsCompletionView: TagsCompletionView
    private var tagsAdapter: FilteredArrayAdapter<String>? = null
    private var mAllowMultipleAttachments: Boolean = false

    private var mIconColor: Int = 0

    override fun onCreateView(inflater: LayoutInflater,
                              container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        super.onCreateView(inflater, container, savedInstanceState)

        // Retrieve the textColor to tint the icon
        context?.obtainStyledAttributes(intArrayOf(android.R.attr.textColor)).also { taIconColor ->
            mIconColor = taIconColor?.getColor(0, Color.BLACK) ?: Color.BLACK
        }?.recycle()

        return inflater.inflate(R.layout.fragment_entry_edit, container, false)
    }

    override fun onViewCreated(
        view: View,
        savedInstanceState: Bundle?
    ) {
        super.onViewCreated(view, savedInstanceState)

        rootView = view
        // Hide only the first time
        if (savedInstanceState == null) {
            view.isVisible = false
        }
        templateView = view.findViewById(R.id.template_view)
        attachmentsContainerView = view.findViewById(R.id.entry_attachments_container)
        attachmentsListView = view.findViewById(R.id.entry_attachments_list)
        tagsContainerView = view.findViewById(R.id.entry_tags_label)
        tagsCompletionView = view.findViewById(R.id.entry_tags_completion_view)

        attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
        attachmentsListView.apply {
            layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
            adapter = attachmentsAdapter
            (itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
        }

        templateView.apply {
            setOnIconClickListener {
                mEntryEditViewModel.requestIconSelection(templateView.getIcon())
            }
            setOnBackgroundColorClickListener {
                mEntryEditViewModel.requestBackgroundColorSelection(templateView.getBackgroundColor())
            }
            setOnForegroundColorClickListener {
                mEntryEditViewModel.requestForegroundColorSelection(templateView.getForegroundColor())
            }
            setOnChangeFieldProtectionClickListener { fieldProtection ->
                mEntryEditViewModel.requestChangeFieldProtection(fieldProtection)
            }
            setOnCustomEditionActionClickListener { field ->
                mEntryEditViewModel.requestCustomFieldEdition(field)
            }
            setOnPasswordGenerationActionClickListener { field ->
                mEntryEditViewModel.requestPasswordSelection(field)
            }
            setOnDateInstantClickListener { dateInstant ->
                mEntryEditViewModel.requestDateTimeSelection(dateInstant)
            }
        }

        if (savedInstanceState != null) {
            val attachments: List<Attachment> =
                savedInstanceState.getParcelableList(ATTACHMENTS_TAG) ?: listOf()
            setAttachments(attachments)
        }

        mEntryEditViewModel.onTemplateChanged.observe(viewLifecycleOwner) { template ->
            templateView.setTemplate(template)
        }

        mEntryEditViewModel.templatesEntry.observe(viewLifecycleOwner) { templateEntry ->
            if (templateEntry != null) {
                val selectedTemplate = templateEntry.template ?: templateEntry.defaultTemplate
                templateView.setTemplate(selectedTemplate)
                // Load entry info only the first time to keep change locally
                if (savedInstanceState == null) {
                    assignEntryInfo(templateEntry.entryInfo)
                }
                // To prevent flickering
                rootView.showByFading()
                // Apply timeout reset
                resetAppTimeoutWhenViewFocusedOrChanged(rootView)
            }
        }

        mEntryEditViewModel.requestEntryInfoUpdate.observe(viewLifecycleOwner) {
            mEntryEditViewModel.saveEntryInfo(retrieveEntryInfo())
        }

        mEntryEditViewModel.onIconSelected.observe(viewLifecycleOwner) { iconImage ->
            templateView.setIcon(iconImage)
        }

        mEntryEditViewModel.onBackgroundColorSelected.observe(viewLifecycleOwner) { color ->
            templateView.setBackgroundColor(color)
        }

        mEntryEditViewModel.onForegroundColorSelected.observe(viewLifecycleOwner) { color ->
            templateView.setForegroundColor(color)
        }

        mEntryEditViewModel.onPasswordSelected.observe(viewLifecycleOwner) { passwordField ->
            templateView.setPasswordField(passwordField)
        }

        mEntryEditViewModel.onDateSelected.observe(viewLifecycleOwner) { viewModelDate ->
            // Save the date
            templateView.setCurrentDateTimeValue(viewModelDate)
        }

        mEntryEditViewModel.onTimeSelected.observe(viewLifecycleOwner) { viewModelTime ->
            // Save the time
            templateView.setCurrentTimeValue(viewModelTime)
        }

        mEntryEditViewModel.onCustomFieldEdited.observe(viewLifecycleOwner) { fieldAction ->
            val oldField = fieldAction.oldField
            val newField = fieldAction.newField
            // Field to add
            if (oldField == null) {
                newField?.let {
                    if (!templateView.putCustomField(it)) {
                        mEntryEditViewModel.showCustomFieldEditionError()
                    }
                }
            }
            // Field to replace
            oldField?.let {
                newField?.let {
                    if (!templateView.replaceCustomField(oldField, newField)) {
                        mEntryEditViewModel.showCustomFieldEditionError()
                    }
                }
            }
            // Field to remove
            if (newField == null) {
                oldField?.let {
                    templateView.removeCustomField(it)
                    // If no more OTP fields, remove the OTP tag
                    if (it.isOTP() && templateView.getEntryInfo().customFields.none { field -> field.isOTP() }) {
                        tagsCompletionView.removeObjectSync(OtpEntryFields.OTP_TAG)
                    }
                }
            }
        }

        mEntryEditViewModel.requestSetupOtp.observe(viewLifecycleOwner) {
            // Retrieve the current otpElement if exists
            // and open the dialog to set up the OTP
            SetOTPDialogFragment.build(templateView.getEntryInfo().otpModel)
                .show(parentFragmentManager, "addOTPDialog")
        }

        mEntryEditViewModel.onOtpCreated.observe(viewLifecycleOwner) {
            // Update the otp field with otpauth:// url
            templateView.putOtpElement(it)
            tagsCompletionView.addObjectSync(OtpEntryFields.OTP_TAG)
        }

        mEntryEditViewModel.onBuildNewAttachment.observe(viewLifecycleOwner) {
            val attachmentToUploadUri = it.attachmentToUploadUri
            val fileName = it.fileName

            mDatabaseViewModel.buildNewAttachment()?.let { binaryAttachment ->
                val entryAttachment = Attachment(fileName, binaryAttachment)
                // Ask to replace the current attachment
                if ((!mAllowMultipleAttachments
                            && containsAttachment()) ||
                    containsAttachment(EntryAttachmentState(entryAttachment, StreamDirection.UPLOAD))) {
                    ReplaceFileDialogFragment.build(attachmentToUploadUri, entryAttachment)
                        .show(parentFragmentManager, "replacementFileFragment")
                } else {
                    mEntryEditViewModel.startUploadAttachment(attachmentToUploadUri, entryAttachment)
                }
            }
        }

        mEntryEditViewModel.onAttachmentAction.observe(viewLifecycleOwner) { entryAttachmentState ->
            when (entryAttachmentState?.downloadState) {
                AttachmentState.START -> {
                    putAttachment(entryAttachmentState)
                    getAttachmentViewPosition(entryAttachmentState) { attachment, position ->
                        mEntryEditViewModel.binaryPreviewLoaded(attachment, position)
                    }
                }
                AttachmentState.IN_PROGRESS -> {
                    putAttachment(entryAttachmentState)
                }
                AttachmentState.COMPLETE -> {
                    putAttachment(entryAttachmentState) { entryAttachment ->
                        getAttachmentViewPosition(entryAttachment) { attachment, position ->
                            mEntryEditViewModel.binaryPreviewLoaded(attachment, position)
                        }
                    }
                    mEntryEditViewModel.onAttachmentAction(null)
                }
                AttachmentState.CANCELED,
                AttachmentState.ERROR -> {
                    removeAttachment(entryAttachmentState)
                    mEntryEditViewModel.onAttachmentAction(null)
                }
                else -> {}
            }
        }

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                mEntryEditViewModel.entryEditState.collect { entryEditState ->
                    when (entryEditState) {
                        is EntryEditViewModel.EntryEditState.Loading -> {}
                        is EntryEditViewModel.EntryEditState.ShowOverwriteMessage -> {}
                        is EntryEditViewModel.EntryEditState.OnChangeFieldProtectionRequested -> {}
                        is EntryEditViewModel.EntryEditState.OnFieldProtectionUpdated -> {
                            updateFieldProtection(entryEditState.fieldProtection)
                            mEntryEditViewModel.actionPerformed()
                        }
                        is EntryEditViewModel.EntryEditState.CloseEntry -> {
                            // Managed in entry edit activity
                        }
                        is EntryEditViewModel.EntryEditState.RetrieveEntryInfoForClosing -> {
                            mEntryEditViewModel.askToCloseEntry(
                                currentEntryInfo = retrieveEntryInfo(),
                                closeType = entryEditState.closeType
                            )
                        }
                        is EntryEditViewModel.EntryEditState.AskToDiscardChanges -> {
                            // Dialog in activity
                        }
                    }
                }
            }
        }
    }

    override fun onDatabaseRetrieved(database: ContextualDatabase) {

        templateView.populateIconMethod = { imageView, icon ->
            database.iconDrawableFactory.assignDatabaseIcon(imageView, icon, mIconColor)
        }

        mAllowMultipleAttachments = database.allowMultipleAttachments == true

        attachmentsAdapter?.database = database
        attachmentsAdapter?.onListSizeChangedListener = { previousSize, newSize ->
            if (previousSize > 0 && newSize == 0) {
                attachmentsContainerView.collapse(true)
            } else if (previousSize == 0 && newSize == 1) {
                attachmentsContainerView.expand(true)
            }
        }

        tagsAdapter = TagsProposalAdapter(
            requireContext(),
            database.tagPoolWithoutHistory
        )
        tagsCompletionView.apply {
            threshold = 1
            setAdapter(tagsAdapter)
        }
        tagsContainerView.visibility = if (database.allowTags()) View.VISIBLE else View.GONE
    }

    private fun assignEntryInfo(entryInfo: EntryInfo?) {
        // Populate entry views
        templateView.setEntryInfo(entryInfo)

        // Set Tags
        entryInfo?.tags?.let { tags ->
            tagsCompletionView.setText("")
            for (i in 0 until tags.size()) {
                tagsCompletionView.addObjectSync(tags.get(i).name)
            }
        }

        // Manage attachments
        setAttachments(entryInfo?.attachments ?: listOf())
    }

    private fun retrieveEntryInfo(): EntryInfo {
        val entryInfo = templateView.getEntryInfo()
        entryInfo.tags = tagsCompletionView.getTags()
        entryInfo.attachments = getAttachments().toMutableList()
        return entryInfo
    }

    private fun updateFieldProtection(fieldProtection: FieldProtection) {
        templateView.setFieldProtection(fieldProtection)
    }

    /* -------------
     * Attachments
     * -------------
     */

    private fun getAttachments(): List<Attachment> {
        return attachmentsAdapter?.itemsList?.map { it.attachment } ?: listOf()
    }

    private fun setAttachments(attachments: List<Attachment>) {
        attachmentsContainerView.visibility = if (attachments.isEmpty()) View.GONE else View.VISIBLE
        attachmentsAdapter?.assignItems(attachments.map {
            EntryAttachmentState(it, StreamDirection.UPLOAD)
        })
        attachmentsAdapter?.onDeleteButtonClickListener = { item ->
            val attachment = item.attachment
            removeAttachment(EntryAttachmentState(attachment, StreamDirection.DOWNLOAD))
            mEntryEditViewModel.deleteAttachment(attachment)
        }
    }

    private fun containsAttachment(): Boolean {
        return attachmentsAdapter?.isEmpty() != true
    }

    private fun containsAttachment(attachment: EntryAttachmentState): Boolean {
        return attachmentsAdapter?.contains(attachment) ?: false
    }

    private fun putAttachment(attachment: EntryAttachmentState,
                              onPreviewLoaded: ((attachment: EntryAttachmentState) -> Unit)? = null) {
        // When only one attachment is allowed
        if (!mAllowMultipleAttachments
            && attachment.downloadState == AttachmentState.START) {
            attachmentsAdapter?.clear()
        }
        attachmentsContainerView.visibility = View.VISIBLE
        attachmentsAdapter?.putItem(attachment)
        attachmentsAdapter?.onBinaryPreviewLoaded = {
            onPreviewLoaded?.invoke(attachment)
        }
    }

    private fun removeAttachment(attachment: EntryAttachmentState) {
        attachmentsAdapter?.removeItem(attachment)
    }

    private fun getAttachmentViewPosition(attachment: EntryAttachmentState,
                                          position: (attachment: EntryAttachmentState, Float) -> Unit) {
        attachmentsListView.postDelayed({
            attachmentsAdapter?.indexOf(attachment)?.let { index ->
                position.invoke(attachment,
                    attachmentsContainerView.y
                        + attachmentsListView.y
                        + (attachmentsListView.getChildAt(index)?.y
                        ?: 0F)
                )
            }
        }, 250)
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putParcelableList(ATTACHMENTS_TAG, getAttachments())
    }

    /* -------------
     * Education
     * -------------
     */

    fun getActionImageView(): View? {
        return templateView.getActionImageView()
    }

    fun launchGeneratePasswordEductionAction() {
        mEntryEditViewModel.requestPasswordSelection(templateView.getPasswordField())
    }

    companion object {
        private val TAG = EntryEditFragment::class.java.name

        private const val ATTACHMENTS_TAG = "ATTACHMENTS_TAG"
    }

}