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

import android.accounts.Account
import android.app.Dialog
import android.app.ProgressDialog
import android.content.ContentResolver
import android.content.Context
import android.os.Bundle
import android.provider.CalendarContract
import androidx.fragment.app.DialogFragment
import androidx.loader.app.LoaderManager
import androidx.loader.content.AsyncTaskLoader
import androidx.loader.content.Loader
import com.etesync.syncadapter.*
import com.etesync.journalmanager.Crypto
import com.etesync.journalmanager.Exceptions
import com.etesync.journalmanager.JournalManager
import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity
import com.etesync.syncadapter.model.JournalModel
import com.etesync.syncadapter.utils.TaskProviderHandling
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull

class CreateCollectionFragment : DialogFragment(), LoaderManager.LoaderCallbacks<Exception> {

    protected lateinit var account: Account
    protected lateinit var info: CollectionInfo

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

        account = arguments!!.getParcelable(ARG_ACCOUNT)
        info = arguments!!.getSerializable(ARG_COLLECTION_INFO) as CollectionInfo

        loaderManager.initLoader(0, null, this)
    }

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


    override fun onCreateLoader(id: Int, args: Bundle?): Loader<Exception> {
        return CreateCollectionLoader(context!!, account, info)
    }

    override fun onLoadFinished(loader: Loader<Exception>, exception: Exception?) {
        dismissAllowingStateLoss()

        val parent = activity
        if (parent != null) {
            if (exception != null)
                fragmentManager!!.beginTransaction()
                        .add(ExceptionInfoFragment.newInstance(exception, account), null)
                        .commitAllowingStateLoss()
            else
                parent.finish()
        }

    }

    override fun onLoaderReset(loader: Loader<Exception>) {}


    protected class CreateCollectionLoader(context: Context, internal val account: Account, internal val info: CollectionInfo) : AsyncTaskLoader<Exception>(context) {

        override fun onStartLoading() {
            forceLoad()
        }

        override fun loadInBackground(): Exception? {
            try {
                val context = context.applicationContext
                val data = (context as App).data

                // 1. find service ID
                val authority = when (info.enumType){
                    CollectionInfo.Type.ADDRESS_BOOK -> App.addressBooksAuthority
                    CollectionInfo.Type.CALENDAR -> CalendarContract.AUTHORITY
                    CollectionInfo.Type.TASKS ->
                        TaskProviderHandling.getWantedTaskSyncProvider(context)?.authority
                    else -> null
                }

                val serviceEntity = JournalModel.Service.fetchOrCreate(data, account.name, info.enumType)
                info.serviceID = serviceEntity.id

                val settings = AccountSettings(context, account)
                val principal = settings.uri?.toHttpUrlOrNull()

                val httpClient = HttpClient.Builder(context, settings).build().okHttpClient
                val journalManager = JournalManager(httpClient, principal!!)
                var uid = info.uid

                if (uid == null) {
                    uid = JournalManager.Journal.genUid()
                    info.uid = uid
                    val crypto = Crypto.CryptoManager(info.version, settings.password(), uid)
                    val journal = JournalManager.Journal(crypto, info.toJson(), uid)
                    journalManager.create(journal)

                    val journalEntity = JournalEntity.fetchOrCreate(data, info)
                    data.upsert(journalEntity)
                } else {
                    val crypto: Crypto.CryptoManager
                    val journalEntity = JournalEntity.fetch(data, serviceEntity, uid)

                    if (journalEntity.encryptedKey != null) {
                        crypto = Crypto.CryptoManager(info.version, settings.keyPair!!, journalEntity.encryptedKey)
                    } else {
                        crypto = Crypto.CryptoManager(info.version, settings.password(), uid)
                    }
                    val journal = JournalManager.Journal(crypto, info.toJson(), uid)
                    journalManager.update(journal)
                }

                authority?.let { requestSync(it) }
            } catch (e: IllegalStateException) {
                return e
            } catch (e: Exceptions.HttpException) {
                return e
            } catch (e: InvalidAccountException) {
                return e
            } catch (e: Exceptions.IntegrityException) {
                return e
            } catch (e: Exceptions.GenericCryptoException) {
                return e
            } catch (e: Exceptions.AssociateNotAllowedException) {
                return e
            } catch (e: Exception) {
                // Also catch all exceptions
                return e
            }

            return null
        }

        private fun requestSync(authority: String) {
            val extras = Bundle()
            extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)        // manual sync
            extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true)     // run immediately (don't queue)
            ContentResolver.requestSync(account, authority, extras)
        }
    }

    companion object {
        private val ARG_ACCOUNT = "account"
        private val ARG_COLLECTION_INFO = "collectionInfo"

        fun newInstance(account: Account, info: CollectionInfo): CreateCollectionFragment {
            val frag = CreateCollectionFragment()
            val args = Bundle(2)
            args.putParcelable(ARG_ACCOUNT, account)
            args.putSerializable(ARG_COLLECTION_INFO, info)
            frag.arguments = args
            return frag
        }
    }

}
