/*
 * Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 */

package com.etesync.syncadapter.ui.setup

import android.accounts.Account
import android.accounts.AccountManager
import android.app.Activity
import android.app.Dialog
import android.app.ProgressDialog
import android.content.Context
import android.os.AsyncTask
import android.os.Bundle
import android.provider.CalendarContract
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import at.bitfire.ical4android.TaskProvider.Companion.OPENTASK_PROVIDERS
import com.etesync.journalmanager.Crypto
import com.etesync.journalmanager.Exceptions
import com.etesync.syncadapter.*
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity
import com.etesync.syncadapter.model.ServiceEntity
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration
import com.etesync.syncadapter.utils.AndroidCompat
import com.etesync.syncadapter.utils.TaskProviderHandling
import java.util.logging.Level

class SetupEncryptionFragment : DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val progress = ProgressDialog(activity)
        progress.setTitle(R.string.setting_up_encryption)
        progress.setMessage(getString(R.string.setting_up_encryption_content))
        progress.isIndeterminate = true
        progress.setCanceledOnTouchOutside(false)
        isCancelable = false
        return progress
    }

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

        SetupEncryptionLoader(context!!, arguments!!.getSerializable(KEY_CONFIG) as Configuration).execute()
    }

    private inner class SetupEncryptionLoader(internal val context: Context, internal val config: Configuration) : AsyncTask<Void, Void, Configuration>() {

        override fun onPostExecute(result: Configuration) {
            if (config.error != null && config.error is Exceptions.IntegrityException) {
                Logger.log.severe("Wrong encryption password.")
                AlertDialog.Builder(activity!!)
                        .setTitle(R.string.wrong_encryption_password)
                        .setIcon(R.drawable.ic_error_dark)
                        .setMessage(getString(R.string.wrong_encryption_password_content, config.error!!.localizedMessage))
                        .setPositiveButton(android.R.string.ok) { _, _ ->
                            // dismiss
                        }.show()
            } else {
                try {
                    if (createAccount(config.userName, config)) {
                        activity!!.setResult(Activity.RESULT_OK)
                        activity!!.finish()
                    }
                } catch (e: InvalidAccountException) {
                    Logger.log.severe("Account creation failed!")
                    AlertDialog.Builder(activity!!)
                            .setTitle(R.string.account_creation_failed)
                            .setIcon(R.drawable.ic_error_dark)
                            .setMessage(e.localizedMessage)
                            .setPositiveButton(android.R.string.ok) { _, _ ->
                                // dismiss
                            }.show()
                }

            }

            dismissAllowingStateLoss()
        }

        override fun doInBackground(vararg aVoids: Void): Configuration {
            Logger.log.info("Started deriving key")
            config.password = Crypto.deriveKey(config.userName, config.rawPassword!!)
            Logger.log.info("Finished deriving key")
            config.error = null

            try {
                val cryptoManager: Crypto.CryptoManager

                val userInfo = config.userInfo
                if (userInfo != null) {
                    Logger.log.info("Fetched userInfo for " + config.userName)
                    cryptoManager = Crypto.CryptoManager(userInfo.version!!.toInt(), config.password!!, "userInfo")
                    userInfo.verify(cryptoManager)
                    config.keyPair = Crypto.AsymmetricKeyPair(userInfo.getContent(cryptoManager)!!, userInfo.pubkey!!)
                }
            } catch (e: Exception) {
                e.printStackTrace()
                config.error = e
            }

            return config
        }
    }


    @Throws(InvalidAccountException::class)
    protected fun createAccount(accountName: String, config: BaseConfigurationFinder.Configuration): Boolean {
        val account = Account(accountName, App.accountType)

        // create Android account
        Logger.log.log(Level.INFO, "Creating Android account with initial config", arrayOf(account, config.userName, config.url))

        val accountManager = AccountManager.get(context)
        if (!accountManager.addAccountExplicitly(account, config.password, null))
            return false

        AccountSettings.setUserData(accountManager, account, config.url, config.userName)

        // add entries for account to service DB
        Logger.log.log(Level.INFO, "Writing account configuration to database", config)
        try {
            val settings = AccountSettings(context!!, account)

            settings.authToken = config.authtoken!!
            if (config.keyPair != null) {
                settings.keyPair = config.keyPair!!
            }

            // insert CardDAV service
            insertService(accountName, CollectionInfo.Type.ADDRESS_BOOK)

            // contact sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
            settings.setSyncInterval(App.addressBooksAuthority, Constants.DEFAULT_SYNC_INTERVAL.toLong())

            // insert CalDAV service
            insertService(accountName, CollectionInfo.Type.CALENDAR)

            // calendar sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
            settings.setSyncInterval(CalendarContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL.toLong())

            OPENTASK_PROVIDERS.forEach {
                // enable task sync if OpenTasks is installed
                // further changes will be handled by PackageChangedReceiver
                TaskProviderHandling.updateTaskSync(context!!, it)
            }

        } catch (e: InvalidAccountException) {
            Logger.log.log(Level.SEVERE, "Couldn't access account settings", e)
            AndroidCompat.removeAccount(accountManager, account)
            throw e
        }

        return true
    }

    protected fun insertService(accountName: String, serviceType: CollectionInfo.Type) {
        val info = Configuration.ServiceInfo()
        val data = (requireContext().applicationContext as App).data

        // insert service
        val serviceEntity = ServiceEntity.fetchOrCreate(data, accountName, serviceType)

        // insert collections
        for (collection in info.collections.values) {
            collection.serviceID = serviceEntity.id
            val journalEntity = JournalEntity(data, collection)
            data.insert(journalEntity)
        }
    }

    companion object {
        private val KEY_CONFIG = "config"

        fun newInstance(config: BaseConfigurationFinder.Configuration): SetupEncryptionFragment {
            val frag = SetupEncryptionFragment()
            val args = Bundle(1)
            args.putSerializable(KEY_CONFIG, config)
            frag.arguments = args
            return frag
        }
    }
}
