/*
 * 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.credentialprovider.activity

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.RequiresApi
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildActivityResultLauncher
import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillComponent
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper
import com.kunzisoft.keepass.credentialprovider.autofill.CompatInlineSuggestionsRequest
import com.kunzisoft.keepass.credentialprovider.autofill.KeeAutofillService
import com.kunzisoft.keepass.database.ContextualDatabase
import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseException
import com.kunzisoft.keepass.database.helper.SearchHelper
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.utils.AppUtil.getConcreteWebDomain
import com.kunzisoft.keepass.utils.getParcelableCompat
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.keepass.view.toastError

@RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : DatabaseModeActivity() {

    override var mCredentialActivityResultLauncher: ActivityResultLauncher<Intent>? =
        this.buildActivityResultLauncher(typeMode = TypeMode.AUTOFILL, lockDatabase = true)

    override fun applyCustomStyle(): Boolean {
        return false
    }

    override fun finishActivityIfReloadRequested(): Boolean {
        return true
    }

    override fun onDatabaseRetrieved(database: ContextualDatabase?) {
        super.onDatabaseRetrieved(database)

        // Retrieve selection mode
        EntrySelectionHelper.retrieveSpecialModeFromIntent(intent).let { specialMode ->
            when (specialMode) {
                SpecialMode.SELECTION -> {
                    intent.getBundleExtra(KEY_SELECTION_BUNDLE)?.let { bundle ->
                        // To pass extra inline request
                        var compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest? = null
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                            compatInlineSuggestionsRequest = bundle.getParcelableCompat(
                                KEY_INLINE_SUGGESTION
                            )
                        }
                        // Build search param
                        bundle.getParcelableCompat<SearchInfo>(KEY_SEARCH_INFO)?.let { searchInfo ->
                            searchInfo.getConcreteWebDomain(this) { concreteWebDomain ->
                                // Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
                                val assistStructure = AutofillHelper
                                    .retrieveAutofillComponent(intent)
                                    ?.assistStructure
                                val newAutofillComponent = if (assistStructure != null) {
                                    AutofillComponent(
                                        assistStructure,
                                        compatInlineSuggestionsRequest
                                    )
                                } else {
                                    null
                                }
                                searchInfo.webDomain = concreteWebDomain
                                launchSelection(database, newAutofillComponent, searchInfo)
                            }
                        }
                    }
                    // Remove bundle
                    intent.removeExtra(KEY_SELECTION_BUNDLE)
                }
                SpecialMode.REGISTRATION -> {
                    // To register info
                    val registerInfo = intent.getParcelableExtraCompat<RegisterInfo>(
                        KEY_REGISTER_INFO
                    )
                    val searchInfo = SearchInfo(registerInfo?.searchInfo)
                    searchInfo.getConcreteWebDomain(this) { concreteWebDomain ->
                        searchInfo.webDomain = concreteWebDomain
                        launchRegistration(database, searchInfo, registerInfo)
                    }
                }
                else -> {
                    // Not an autofill call
                    setResult(RESULT_CANCELED)
                    finish()
                }
            }
        }
    }

    private fun launchSelection(database: ContextualDatabase?,
                                autofillComponent: AutofillComponent?,
                                searchInfo: SearchInfo) {
        if (autofillComponent == null) {
            setResult(RESULT_CANCELED)
            finish()
        } else if (KeeAutofillService.autofillAllowedFor(
            applicationId = searchInfo.applicationId,
            webDomain = searchInfo.webDomain,
            context = this
        )) {
            // If database is open
            SearchHelper.checkAutoSearchInfo(
                context = this,
                database = database,
                searchInfo = searchInfo,
                onItemsFound = { openedDatabase, items ->
                    // Items found
                    AutofillHelper.buildResponseAndSetResult(this, openedDatabase, items)
                    finish()
                },
                onItemNotFound = { openedDatabase ->
                    // Show the database UI to select the entry
                    GroupActivity.launchForSelection(
                        context = this,
                        database = openedDatabase,
                        typeMode = TypeMode.AUTOFILL,
                        searchInfo = searchInfo,
                        autoSearch = false,
                        autofillComponent = autofillComponent,
                        activityResultLauncher = mCredentialActivityResultLauncher,
                    )
                },
                onDatabaseClosed = {
                    // If database not open
                    FileDatabaseSelectActivity.launchForSelection(
                        activity = this,
                        typeMode = TypeMode.AUTOFILL,
                        searchInfo = searchInfo,
                        autofillComponent = autofillComponent,
                        activityResultLauncher = mCredentialActivityResultLauncher
                    )
                }
            )
        } else {
            showBlockRestartMessage()
            setResult(RESULT_CANCELED)
            finish()
        }
    }

    private fun launchRegistration(database: ContextualDatabase?,
                                   searchInfo: SearchInfo,
                                   registerInfo: RegisterInfo?) {
        if (KeeAutofillService.autofillAllowedFor(
                applicationId = searchInfo.applicationId,
                webDomain = searchInfo.webDomain,
                context = this
        )) {
            val readOnly = database?.isReadOnly != false
            SearchHelper.checkAutoSearchInfo(
                context = this,
                database = database,
                searchInfo = searchInfo,
                onItemsFound = { openedDatabase, _ ->
                    if (!readOnly) {
                        // Show the database UI to select the entry
                        GroupActivity.launchForRegistration(
                            context = this,
                            activityResultLauncher = null, // TODO Autofill result launcher #765
                            database = openedDatabase,
                            registerInfo = registerInfo,
                            typeMode = TypeMode.AUTOFILL
                        )
                    } else {
                        showReadOnlySaveMessage()
                    }
                },
                onItemNotFound = { openedDatabase ->
                    if (!readOnly) {
                        // Show the database UI to select the entry
                        GroupActivity.launchForRegistration(
                            context = this,
                            activityResultLauncher = null, // TODO Autofill result launcher #765
                            database = openedDatabase,
                            registerInfo = registerInfo,
                            typeMode = TypeMode.AUTOFILL
                        )
                    } else {
                        showReadOnlySaveMessage()
                    }
                },
                onDatabaseClosed = {
                    // If database not open
                    FileDatabaseSelectActivity.launchForRegistration(
                        context = this,
                        activityResultLauncher = null, // TODO Autofill result launcher #765
                        registerInfo = registerInfo,
                        typeMode = TypeMode.AUTOFILL
                    )
                }
            )
        } else {
            showBlockRestartMessage()
            setResult(RESULT_CANCELED)
        }
        finish()
    }

    private fun showBlockRestartMessage() {
        // If item not allowed, show a toast
        Toast.makeText(this.applicationContext, R.string.autofill_block_restart, Toast.LENGTH_LONG).show()
    }

    private fun showReadOnlySaveMessage() {
        toastError(RegisterInReadOnlyDatabaseException())
    }

    companion object {

        private val TAG = AutofillLauncherActivity::class.java.name

        private const val KEY_SELECTION_BUNDLE = "KEY_SELECTION_BUNDLE"
        private const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
        private const val KEY_INLINE_SUGGESTION = "KEY_INLINE_SUGGESTION"

        private const val KEY_REGISTER_INFO = "KEY_REGISTER_INFO"

        fun getPendingIntentForSelection(context: Context,
                                         searchInfo: SearchInfo? = null,
                                         compatInlineSuggestionsRequest: CompatInlineSuggestionsRequest? = null): PendingIntent? {
            try {
                return PendingIntent.getActivity(
                    context, 0,
                    // Doesn't work with direct extra Parcelable (don't know why?)
                    // Wrap into a bundle to bypass the problem
                    Intent(context, AutofillLauncherActivity::class.java).apply {
                        putExtra(KEY_SELECTION_BUNDLE, Bundle().apply {
                            putParcelable(KEY_SEARCH_INFO, searchInfo)
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                                putParcelable(KEY_INLINE_SUGGESTION, compatInlineSuggestionsRequest)
                            }
                        })
                    },
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                        PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
                    } else {
                        PendingIntent.FLAG_CANCEL_CURRENT
                    }
                )
            } catch (e: RuntimeException) {
                Log.e(TAG, "Unable to create pending intent for selection", e)
                return null
            }
        }

        fun getPendingIntentForRegistration(context: Context,
                                            registerInfo: RegisterInfo): PendingIntent? {
            try {
                return PendingIntent.getActivity(
                    context, 0,
                    Intent(context, AutofillLauncherActivity::class.java).apply {
                        EntrySelectionHelper.addSpecialModeInIntent(this, SpecialMode.REGISTRATION)
                        putExtra(KEY_REGISTER_INFO, registerInfo)
                    },
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                        PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_CANCEL_CURRENT
                    } else {
                        PendingIntent.FLAG_CANCEL_CURRENT
                    }
                )
            } catch (e: RuntimeException) {
                Log.e(TAG, "Unable to create pending intent for registration", e)
                return null
            }
        }

        fun launchForRegistration(context: Context,
                                  registerInfo: RegisterInfo) {
            val intent = Intent(context, AutofillLauncherActivity::class.java)
            EntrySelectionHelper.addSpecialModeInIntent(intent, SpecialMode.REGISTRATION)
            intent.putExtra(KEY_REGISTER_INFO, registerInfo)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            context.startActivity(intent)
        }
    }
}
