/*
 * 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

import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.graphics.ColorUtils
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.tabs.TabLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.fragments.EntryFragment
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity
import com.kunzisoft.keepass.adapters.TagsAdapter
import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService
import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryActivityEducation
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.otp.OtpType
import com.kunzisoft.keepass.services.AttachmentFileNotificationService
import com.kunzisoft.keepass.services.ClipboardEntryNotificationService
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.UUIDUtils.asHexString
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.view.WindowInsetPosition
import com.kunzisoft.keepass.view.applyWindowInsets
import com.kunzisoft.keepass.view.changeControlColor
import com.kunzisoft.keepass.view.changeTitleColor
import com.kunzisoft.keepass.view.hideByFading
import com.kunzisoft.keepass.view.setTransparentNavigationBar
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
import com.kunzisoft.keepass.viewmodels.EntryViewModel
import java.util.EnumSet
import java.util.UUID

class EntryActivity : DatabaseLockActivity() {

    private var footer: ViewGroup? = null
    private var container: View? = null
    private var coordinatorLayout: CoordinatorLayout? = null
    private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
    private var appBarLayout: AppBarLayout? = null
    private var titleIconView: ImageView? = null
    private var historyView: View? = null
    private var tagsListView: RecyclerView? = null
    private var entryContentTab: TabLayout? = null
    private var tagsAdapter: TagsAdapter? = null
    private var entryProgress: LinearProgressIndicator? = null
    private var lockView: View? = null
    private var toolbar: Toolbar? = null
    private var loadingView: ProgressBar? = null

    private val mEntryViewModel: EntryViewModel by viewModels()

    private val mEntryActivityEducation = EntryActivityEducation(this)

    private var mMainEntryId: NodeId<UUID>? = null
    private var mHistoryPosition: Int = -1
    private var mEntryIsHistory: Boolean = false
    private var mEntryLoaded = false

    private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
    private var mExternalFileHelper: ExternalFileHelper? = null
    private var mAttachmentSelected: Attachment? = null

    private var mEntryActivityResultLauncher = EntryEditActivity.registerForEntryResult(this) {
        // Reload the current id from database
        mEntryViewModel.loadDatabase(mDatabase)
    }

    private var mIcon: IconImage? = null
    private var mColorSecondary: Int = 0
    private var mColorSurface: Int = 0
    private var mColorOnSurface: Int = 0
    private var mColorBackground: Int = 0
    private var mBackgroundColor: Int? = null
    private var mForegroundColor: Int? = null

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

        setContentView(R.layout.activity_entry)

        toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setDisplayShowHomeEnabled(true)

        // Get views
        footer = findViewById(R.id.activity_entry_footer)
        container = findViewById(R.id.activity_entry_container)
        coordinatorLayout = findViewById(R.id.toolbar_coordinator)
        collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
        appBarLayout = findViewById(R.id.app_bar)
        titleIconView = findViewById(R.id.entry_icon)
        historyView = findViewById(R.id.history_container)
        tagsListView = findViewById(R.id.entry_tags_list_view)
        entryContentTab = findViewById(R.id.entry_content_tab)
        entryProgress = findViewById(R.id.entry_progress)
        lockView = findViewById(R.id.lock_button)
        loadingView = findViewById(R.id.loading)

        // To apply fit window with transparency
        setTransparentNavigationBar {
            // To fix margin with API 27
            ViewCompat.setOnApplyWindowInsetsListener(collapsingToolbarLayout!!, null)
            container?.applyWindowInsets(EnumSet.of(
                WindowInsetPosition.TOP_MARGINS,
                WindowInsetPosition.BOTTOM_MARGINS,
                WindowInsetPosition.START_MARGINS,
                WindowInsetPosition.END_MARGINS,
            ))
        }

        // Empty title
        collapsingToolbarLayout?.title = " "
        toolbar?.title = " "

        // Retrieve the textColor to tint the toolbar
        val taColorSecondary = theme.obtainStyledAttributes(intArrayOf(R.attr.colorSecondary))
        val taColorSurface = theme.obtainStyledAttributes(intArrayOf(R.attr.colorSurface))
        val taColorOnSurface = theme.obtainStyledAttributes(intArrayOf(R.attr.colorOnSurface))
        val taColorBackground = theme.obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
        mColorSecondary = taColorSecondary.getColor(0, Color.BLACK)
        mColorSurface = taColorSurface.getColor(0, Color.BLACK)
        mColorOnSurface = taColorOnSurface.getColor(0, Color.BLACK)
        mColorBackground = taColorBackground.getColor(0, Color.BLACK)
        taColorSecondary.recycle()
        taColorSurface.recycle()
        taColorOnSurface.recycle()
        taColorBackground.recycle()

        // Init Tags adapter
        tagsAdapter = TagsAdapter(this)
        tagsListView?.apply {
            layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
            adapter = tagsAdapter
        }

        // Init content tab
        entryContentTab?.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabSelected(tab: TabLayout.Tab?) {
                mEntryViewModel.selectSection(EntryViewModel.EntrySection.
                    getEntrySectionByPosition(tab?.position ?: 0)
                )
            }

            override fun onTabUnselected(tab: TabLayout.Tab?) {}

            override fun onTabReselected(tab: TabLayout.Tab?) {}
        })

        // Get Entry from UUID
        try {
            intent.getParcelableExtraCompat<NodeId<UUID>>(KEY_ENTRY)?.let { mainEntryId ->
                intent.removeExtra(KEY_ENTRY)
                val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1)
                intent.removeExtra(KEY_ENTRY_HISTORY_POSITION)

                mEntryViewModel.loadEntry(mDatabase, mainEntryId, historyPosition)
            }
        } catch (e: ClassCastException) {
            Log.e(TAG, "Unable to retrieve the entry key")
        }

        // Init SAF manager
        mExternalFileHelper = ExternalFileHelper(this)
        mExternalFileHelper?.buildCreateDocument { createdFileUri ->
            mAttachmentSelected?.let { attachment ->
                if (createdFileUri != null) {
                    mAttachmentFileBinderManager
                        ?.startDownloadAttachment(createdFileUri, attachment)
                }
                mAttachmentSelected = null
            }
        }
        // Init attachment service binder manager
        mAttachmentFileBinderManager = AttachmentFileBinderManager(this)

        lockView?.setOnClickListener {
            lockAndExit()
        }

        mEntryViewModel.sectionSelected.observe(this) { entrySection ->
            entryContentTab?.getTabAt(entrySection.position)?.select()
        }

        mEntryViewModel.entryInfoHistory.observe(this) { entryInfoHistory ->
            if (entryInfoHistory != null) {
                this.mMainEntryId = entryInfoHistory.mainEntryId

                // Manage history position
                val historyPosition = entryInfoHistory.historyPosition
                this.mHistoryPosition = historyPosition
                val entryIsHistory = historyPosition > -1
                this.mEntryIsHistory = entryIsHistory
                // Assign history dedicated view
                historyView?.visibility = if (entryIsHistory) View.VISIBLE else View.GONE
                // TODO History badge
                /*
                if (entryIsHistory) {
                }*/

                val entryInfo = entryInfoHistory.entryInfo
                // Manage entry copy to start notification if allowed (at the first start)
                if (savedInstanceState == null) {
                    // Manage entry to launch copying notification if allowed
                    ClipboardEntryNotificationService.checkAndLaunchNotification(this, entryInfo)
                    // Manage entry to populate Magikeyboard and launch keyboard notification if allowed
                    if (PreferencesUtil.isKeyboardEntrySelectionEnable(this)) {
                        MagikeyboardService.addEntryAndLaunchNotificationIfAllowed(this, entryInfo)
                    }
                }
                // Assign title icon
                mIcon = entryInfo.icon
                // Assign title text
                val entryTitle =
                    entryInfo.title.ifEmpty { entryInfo.id.asHexString() }
                collapsingToolbarLayout?.title = entryTitle
                toolbar?.title = entryTitle
                // Assign tags
                val tags = entryInfo.tags
                tagsListView?.visibility = if (tags.isEmpty()) View.GONE else View.VISIBLE
                tagsAdapter?.setTags(tags)
                // Assign colors
                val showEntryColors = PreferencesUtil.showEntryColors(this)
                mBackgroundColor = if (showEntryColors) entryInfo.backgroundColor else null
                mForegroundColor = if (showEntryColors) entryInfo.foregroundColor else null

                loadingView?.hideByFading()
                mEntryLoaded = true
            } else {
                finish()
            }
            // Refresh Menu
            invalidateOptionsMenu()
        }

        mEntryViewModel.onOtpElementUpdated.observe(this) { otpElement ->
            if (otpElement == null) {
                entryProgress?.visibility = View.GONE
            } else when (otpElement.type) {
                // Only add token if HOTP
                OtpType.HOTP -> {
                    entryProgress?.visibility = View.GONE
                }
                // Refresh view if TOTP
                OtpType.TOTP -> {
                    entryProgress?.apply {
                        max = otpElement.period
                        setProgressCompat(otpElement.secondsRemaining, true)
                        visibility = View.VISIBLE
                    }
                }
            }
        }

        mEntryViewModel.attachmentSelected.observe(this) { attachmentSelected ->
            mAttachmentSelected = attachmentSelected
            mExternalFileHelper?.createDocument(attachmentSelected.name)
        }

        mEntryViewModel.historySelected.observe(this) { historySelected ->
            mDatabase?.let { database ->
                launch(
                    activity = this,
                    database = database,
                    entryId = historySelected.nodeId,
                    historyPosition = historySelected.historyPosition,
                    activityResultLauncher = mEntryActivityResultLauncher
                )
            }
        }
    }

    override fun finishActivityIfReloadRequested(): Boolean {
        return true
    }

    override fun viewToInvalidateTimeout(): View? {
        return coordinatorLayout
    }

    override fun onDatabaseRetrieved(database: ContextualDatabase) {
        super.onDatabaseRetrieved(database)
        mEntryViewModel.loadDatabase(database)
    }

    override fun onDatabaseActionFinished(
        database: ContextualDatabase,
        actionTask: String,
        result: ActionRunnable.Result
    ) {
        super.onDatabaseActionFinished(database, actionTask, result)
        when (actionTask) {
            ACTION_DATABASE_RESTORE_ENTRY_HISTORY,
            ACTION_DATABASE_DELETE_ENTRY_HISTORY -> {
                // Close the current activity after an history action
                if (result.isSuccess)
                    finish()
            }
        }
        coordinatorLayout?.showActionErrorIfNeeded(result)
    }

    override fun onResume() {
        super.onResume()

        // Show the lock button
        lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
            View.VISIBLE
        } else {
            View.GONE
        }

        mAttachmentFileBinderManager?.apply {
            registerProgressTask()
            onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
                override fun onAttachmentAction(fileUri: Uri, entryAttachmentState: EntryAttachmentState) {
                    mEntryViewModel.onAttachmentAction(entryAttachmentState)
                }
            }
        }

        // Keep the screen on
        if (PreferencesUtil.isKeepScreenOnEnabled(this)) {
            window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        }
    }

    override fun onPause() {
        mAttachmentFileBinderManager?.unregisterProgressTask()

        super.onPause()
    }

    private fun applyToolbarColors() {
        collapsingToolbarLayout?.setBackgroundColor(mBackgroundColor ?: mColorSurface)
        collapsingToolbarLayout?.contentScrim = ColorDrawable(mBackgroundColor ?: mColorSurface)
        val backgroundDarker = if (mBackgroundColor != null) {
            ColorUtils.blendARGB(mBackgroundColor!!, Color.WHITE, 0.1f)
        } else {
            mColorBackground
        }
        titleIconView?.background?.colorFilter = BlendModeColorFilterCompat
            .createBlendModeColorFilterCompat(backgroundDarker, BlendModeCompat.SRC_IN)
        mIcon?.let { icon ->
            titleIconView?.let { iconView ->
                mDatabase?.iconDrawableFactory?.assignDatabaseIcon(
                    iconView,
                    icon,
                    mForegroundColor ?: mColorSecondary
                )
            }
        }
        toolbar?.changeControlColor(mForegroundColor ?: mColorOnSurface)
        collapsingToolbarLayout?.changeTitleColor(mForegroundColor ?: mColorOnSurface)
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        if (mEntryLoaded) {
            val inflater = menuInflater

            inflater.inflate(R.menu.entry, menu)
            inflater.inflate(R.menu.database, menu)

            if (mEntryIsHistory && !mDatabaseReadOnly) {
                inflater.inflate(R.menu.entry_history, menu)
            }

            // Show education views
            Handler(Looper.getMainLooper()).post {
                performedNextEducation(menu)
            }
        }
        return true
    }

    override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
        if (mEntryIsHistory || mDatabaseReadOnly) {
            menu?.findItem(R.id.menu_save_database)?.isVisible = false
            menu?.findItem(R.id.menu_merge_database)?.isVisible = false
            menu?.findItem(R.id.menu_edit)?.isVisible = false
        }
        if (!mMergeDataAllowed) {
            menu?.findItem(R.id.menu_merge_database)?.isVisible = false
        }
        if (mSpecialMode != SpecialMode.DEFAULT) {
            menu?.findItem(R.id.menu_merge_database)?.isVisible = false
            menu?.findItem(R.id.menu_reload_database)?.isVisible = false
        }
        applyToolbarColors()
        return super.onPrepareOptionsMenu(menu)
    }

    private fun performedNextEducation(menu: Menu) {
        val entryFragment = supportFragmentManager.findFragmentByTag(ENTRY_FRAGMENT_TAG)
                as? EntryFragment?
        val entryFieldCopyView: View? = entryFragment?.firstEntryFieldCopyView()
        val entryCopyEducationPerformed = entryFieldCopyView != null
                && mEntryActivityEducation.checkAndPerformedEntryCopyEducation(
                entryFieldCopyView,
                {
                    entryFragment.launchEntryCopyEducationAction()
                },
                {
                    performedNextEducation(menu)
                })

        if (!entryCopyEducationPerformed) {
            val menuEditView = toolbar?.findViewById<View>(R.id.menu_edit)
            // entryEditEducationPerformed
            menuEditView != null && mEntryActivityEducation.checkAndPerformedEntryEditEducation(
                    menuEditView,
                    {
                        onOptionsItemSelected(menu.findItem(R.id.menu_edit))
                    },
                    {
                        performedNextEducation(menu)
                    }
            )
        }
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.menu_edit -> {
                mDatabase?.let { database ->
                    mMainEntryId?.let { entryId ->
                        EntryEditActivity.launch(
                            activity = this,
                            database = database,
                            registrationType = EntryEditActivity.RegistrationType.UPDATE,
                            nodeId = entryId,
                            activityResultLauncher = mEntryActivityResultLauncher
                        )
                    }
                }
                return true
            }
            R.id.menu_restore_entry_history -> {
                mMainEntryId?.let { mainEntryId ->
                    restoreEntryHistory(
                        mainEntryId,
                        mHistoryPosition)
                }
            }
            R.id.menu_delete_entry_history -> {
                mMainEntryId?.let { mainEntryId ->
                    deleteEntryHistory(
                        mainEntryId,
                        mHistoryPosition)
                }
            }
            R.id.menu_save_database -> {
                saveDatabase()
            }
            R.id.menu_merge_database -> {
                mergeDatabase()
            }
            R.id.menu_reload_database -> {
                reloadDatabase()
            }
            android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
        }
        return super.onOptionsItemSelected(item)
    }

    override fun finish() {
        // Transit data in previous Activity after an update
        Intent().apply {
            putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mMainEntryId)
            setResult(Activity.RESULT_OK, this)
        }
        super.finish()
    }

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

        const val KEY_ENTRY = "KEY_ENTRY"
        const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION"

        const val ENTRY_FRAGMENT_TAG = "ENTRY_FRAGMENT_TAG"

        /**
         * Open standard or history Entry activity
         */
        fun launch(
            activity: Activity,
            database: ContextualDatabase,
            entryId: NodeId<UUID>,
            historyPosition: Int? = null,
            activityResultLauncher: ActivityResultLauncher<Intent>
        ) {
            if (database.loaded) {
                if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
                    val intent = Intent(activity, EntryActivity::class.java)
                    intent.putExtra(KEY_ENTRY, entryId)
                    historyPosition?.let {
                        intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition)
                    }
                    activityResultLauncher.launch(intent)
                }
            }
        }
    }
}
