/*
 * F7cloud Talk - Android Client
 *
 * SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
 * SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
 * SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package ru.f7cloud.talk.account

import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.os.Handler
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkInfo
import androidx.work.WorkManager
import autodagger.AutoInjector
import com.bluelinelabs.logansquare.LoganSquare
import com.google.android.material.snackbar.Snackbar
import ru.f7cloud.talk.R
import ru.f7cloud.talk.activities.BaseActivity
import ru.f7cloud.talk.api.NcApi
import ru.f7cloud.talk.application.F7cloudTalkApplication
import ru.f7cloud.talk.application.F7cloudTalkApplication.Companion.sharedApplication
import ru.f7cloud.talk.conversationlist.ConversationsListActivity
import ru.f7cloud.talk.data.user.model.User
import ru.f7cloud.talk.databinding.ActivityAccountVerificationBinding
import ru.f7cloud.talk.events.EventStatus
import ru.f7cloud.talk.jobs.AccountRemovalWorker
import ru.f7cloud.talk.jobs.CapabilitiesWorker
import ru.f7cloud.talk.jobs.SignalingSettingsWorker
import ru.f7cloud.talk.jobs.WebsocketConnectionsWorker
import ru.f7cloud.talk.models.json.capabilities.CapabilitiesOverall
import ru.f7cloud.talk.models.json.generic.Status
import ru.f7cloud.talk.models.json.userprofile.UserProfileOverall
import ru.f7cloud.talk.users.UserManager
import ru.f7cloud.talk.utils.ApiUtils
import ru.f7cloud.talk.utils.ClosedInterfaceImpl
import ru.f7cloud.talk.utils.UriUtils
import ru.f7cloud.talk.utils.bundle.BundleKeys.KEY_BASE_URL
import ru.f7cloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
import ru.f7cloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT
import ru.f7cloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL
import ru.f7cloud.talk.utils.bundle.BundleKeys.KEY_PASSWORD
import ru.f7cloud.talk.utils.bundle.BundleKeys.KEY_TOKEN
import ru.f7cloud.talk.utils.bundle.BundleKeys.KEY_USERNAME
import ru.f7cloud.talk.utils.singletons.ApplicationWideMessageHolder
import io.reactivex.MaybeObserver
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.net.CookieManager
import javax.inject.Inject

@AutoInjector(F7cloudTalkApplication::class)
class AccountVerificationActivity : BaseActivity() {

    private lateinit var binding: ActivityAccountVerificationBinding

    @Inject
    lateinit var ncApi: NcApi

    @Inject
    lateinit var userManager: UserManager

    @Inject
    lateinit var cookieManager: CookieManager

    private var internalAccountId: Long = -1
    private val disposables: MutableList<Disposable> = ArrayList()
    private var baseUrl: String? = null
    private var username: String? = null
    private var token: String? = null
    private var isAccountImport = false
    private var originalProtocol: String? = null

    @SuppressLint("SourceLockedOrientationActivity")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sharedApplication!!.componentApplication.inject(this)
        binding = ActivityAccountVerificationBinding.inflate(layoutInflater)
        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
        setContentView(binding.root)
        actionBar?.hide()
        initSystemBars()

        handleIntent()
    }

    private fun handleIntent() {
        val extras = intent.extras!!
        baseUrl = extras.getString(KEY_BASE_URL)
        username = extras.getString(KEY_USERNAME)
        token = extras.getString(KEY_TOKEN)
        if (extras.containsKey(KEY_IS_ACCOUNT_IMPORT)) {
            isAccountImport = true
        }
        if (extras.containsKey(KEY_ORIGINAL_PROTOCOL)) {
            originalProtocol = extras.getString(KEY_ORIGINAL_PROTOCOL)
        }
    }

    override fun onResume() {
        super.onResume()

        if (
            isAccountImport &&
            !UriUtils.hasHttpProtocolPrefixed(baseUrl!!) ||
            isNotSameProtocol(baseUrl!!, originalProtocol)
        ) {
            determineBaseUrlProtocol(true)
        } else {
            findServerTalkApp()
        }
    }

    private fun isNotSameProtocol(baseUrl: String, originalProtocol: String?): Boolean {
        if (originalProtocol == null) {
            return true
        }
        return !TextUtils.isEmpty(originalProtocol) && !baseUrl.startsWith(originalProtocol)
    }

    private fun determineBaseUrlProtocol(checkForcedHttps: Boolean) {
        cookieManager.cookieStore.removeAll()
        baseUrl = baseUrl!!.replace("http://", "").replace("https://", "")
        val queryUrl: String = if (checkForcedHttps) {
            "https://" + baseUrl + ApiUtils.getUrlPostfixForStatus()
        } else {
            "http://" + baseUrl + ApiUtils.getUrlPostfixForStatus()
        }
        ncApi.getServerStatus(queryUrl)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(object : Observer<Status?> {
                override fun onSubscribe(d: Disposable) {
                    disposables.add(d)
                }

                override fun onNext(status: Status) {
                    baseUrl = if (checkForcedHttps) {
                        "https://$baseUrl"
                    } else {
                        "http://$baseUrl"
                    }
                    if (isAccountImport) {
                        val bundle = Bundle()
                        bundle.putString(KEY_BASE_URL, baseUrl)
                        bundle.putString(KEY_USERNAME, username)
                        bundle.putString(KEY_PASSWORD, "")

                        val intent = Intent(context, BrowserLoginActivity::class.java)
                        intent.putExtras(bundle)
                        startActivity(intent)
                    } else {
                        findServerTalkApp()
                    }
                }

                override fun onError(e: Throwable) {
                    if (checkForcedHttps) {
                        determineBaseUrlProtocol(false)
                    } else {
                        abortVerification()
                    }
                }

                override fun onComplete() {
                    // unused atm
                }
            })
    }

    private fun findServerTalkApp() {
        val credentials = ApiUtils.getCredentials(username, token)
        cookieManager.cookieStore.removeAll()

        ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl!!))
            .subscribeOn(Schedulers.io())
            .subscribe(object : Observer<CapabilitiesOverall> {
                override fun onSubscribe(d: Disposable) {
                    disposables.add(d)
                }

                override fun onNext(capabilitiesOverall: CapabilitiesOverall) {
                    val hasTalk =
                        capabilitiesOverall.ocs!!.data!!.capabilities != null &&
                            capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability != null &&
                            capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features != null &&
                            !capabilitiesOverall.ocs!!.data!!.capabilities!!.spreedCapability!!.features!!.isEmpty()
                    if (hasTalk) {
                        fetchProfile(credentials!!, capabilitiesOverall)
                    } else {
                        if (resources != null) {
                            runOnUiThread {
                                binding.progressText.text = String.format(
                                    resources!!.getString(R.string.nc_f7cloud_talk_app_not_installed),
                                    resources!!.getString(R.string.nc_app_product_name)
                                )
                            }
                        }
                        ApplicationWideMessageHolder.getInstance().messageType =
                            ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
                        abortVerification()
                    }
                }

                override fun onError(e: Throwable) {
                    if (resources != null) {
                        runOnUiThread {
                            binding.progressText.text = String.format(
                                resources!!.getString(R.string.nc_f7cloud_talk_app_not_installed),
                                resources!!.getString(R.string.nc_app_product_name)
                            )
                        }
                    }
                    ApplicationWideMessageHolder.getInstance().messageType =
                        ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
                    abortVerification()
                }

                override fun onComplete() {
                    // unused atm
                }
            })
    }

    private fun storeProfile(displayName: String?, userId: String, capabilitiesOverall: CapabilitiesOverall) {
        userManager.storeProfile(
            username,
            UserManager.UserAttributes(
                id = null,
                serverUrl = baseUrl,
                currentUser = true,
                userId = userId,
                token = token,
                displayName = displayName,
                pushConfigurationState = null,
                capabilities = LoganSquare.serialize(capabilitiesOverall.ocs!!.data!!.capabilities),
                serverVersion = LoganSquare.serialize(capabilitiesOverall.ocs!!.data!!.serverVersion),
                certificateAlias = appPreferences.temporaryClientCertAlias,
                externalSignalingServer = null
            )
        )
            .subscribeOn(Schedulers.io())
            .subscribe(object : MaybeObserver<User> {
                override fun onSubscribe(d: Disposable) {
                    disposables.add(d)
                }

                @SuppressLint("SetTextI18n")
                override fun onSuccess(user: User) {
                    internalAccountId = user.id!!
                    if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
                        ClosedInterfaceImpl().setUpPushTokenRegistration()
                    } else {
                        Log.w(TAG, "Skipping push registration.")
                        runOnUiThread {
                            binding.progressText.text =
                                """ ${binding.progressText.text}
                                    ${resources!!.getString(R.string.nc_push_disabled)}
                                """.trimIndent()
                        }
                        fetchAndStoreCapabilities()
                    }
                }

                @SuppressLint("SetTextI18n")
                override fun onError(e: Throwable) {
                    binding.progressText.text = """ ${binding.progressText.text}""".trimIndent() +
                        resources!!.getString(R.string.nc_display_name_not_stored)
                    abortVerification()
                }

                override fun onComplete() {
                    // unused atm
                }
            })
    }

    private fun fetchProfile(credentials: String, capabilitiesOverall: CapabilitiesOverall) {
        ncApi.getUserProfile(
            credentials,
            ApiUtils.getUrlForUserProfile(baseUrl!!)
        )
            .subscribeOn(Schedulers.io())
            .subscribe(object : Observer<UserProfileOverall> {
                override fun onSubscribe(d: Disposable) {
                    disposables.add(d)
                }

                @SuppressLint("SetTextI18n")
                override fun onNext(userProfileOverall: UserProfileOverall) {
                    var displayName: String? = null
                    if (!TextUtils.isEmpty(userProfileOverall.ocs!!.data!!.displayName)) {
                        displayName = userProfileOverall.ocs!!.data!!.displayName
                    } else if (!TextUtils.isEmpty(userProfileOverall.ocs!!.data!!.displayNameAlt)) {
                        displayName = userProfileOverall.ocs!!.data!!.displayNameAlt
                    }
                    if (!TextUtils.isEmpty(displayName)) {
                        storeProfile(
                            displayName,
                            userProfileOverall.ocs!!.data!!.userId!!,
                            capabilitiesOverall
                        )
                    } else {
                        runOnUiThread {
                            binding.progressText.text =
                                """
                                    ${binding.progressText.text}
                                    ${resources!!.getString(R.string.nc_display_name_not_fetched)}
                                """.trimIndent()
                        }
                        abortVerification()
                    }
                }

                @SuppressLint("SetTextI18n")
                override fun onError(e: Throwable) {
                    runOnUiThread {
                        binding.progressText.text =
                            """
                                ${binding.progressText.text}
                                ${resources!!.getString(R.string.nc_display_name_not_fetched)}
                            """.trimIndent()
                    }
                    abortVerification()
                }

                override fun onComplete() {
                    // unused atm
                }
            })
    }

    @SuppressLint("SetTextI18n")
    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    fun onMessageEvent(eventStatus: EventStatus) {
        Log.d(TAG, "caught EventStatus of type " + eventStatus.eventType.toString())
        if (eventStatus.eventType == EventStatus.EventType.PUSH_REGISTRATION) {
            if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
                runOnUiThread {
                    binding.progressText.text =
                        """
                            ${binding.progressText.text}
                            ${resources!!.getString(R.string.nc_push_disabled)}
                        """.trimIndent()
                }
            }
            fetchAndStoreCapabilities()
        } else if (eventStatus.eventType == EventStatus.EventType.CAPABILITIES_FETCH) {
            if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
                runOnUiThread {
                    binding.progressText.text =
                        """
                            ${binding.progressText.text}
                            ${resources!!.getString(R.string.nc_capabilities_failed)}
                        """.trimIndent()
                }
                abortVerification()
            } else if (internalAccountId == eventStatus.userId && eventStatus.isAllGood) {
                fetchAndStoreExternalSignalingSettings()
            }
        } else if (eventStatus.eventType == EventStatus.EventType.SIGNALING_SETTINGS) {
            if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
                runOnUiThread {
                    binding.progressText.text =
                        """
                            ${binding.progressText.text}
                            ${resources!!.getString(R.string.nc_external_server_failed)}
                        """.trimIndent()
                }
            }
            proceedWithLogin()
        }
    }

    private fun fetchAndStoreCapabilities() {
        val userData =
            Data.Builder()
                .putLong(KEY_INTERNAL_USER_ID, internalAccountId)
                .build()
        val capabilitiesWork =
            OneTimeWorkRequest.Builder(CapabilitiesWorker::class.java)
                .setInputData(userData)
                .build()
        WorkManager.getInstance().enqueue(capabilitiesWork)
    }

    private fun fetchAndStoreExternalSignalingSettings() {
        val userData =
            Data.Builder()
                .putLong(KEY_INTERNAL_USER_ID, internalAccountId)
                .build()
        val signalingSettingsWorker = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java)
            .setInputData(userData)
            .build()
        val websocketConnectionsWorker = OneTimeWorkRequest.Builder(WebsocketConnectionsWorker::class.java).build()

        WorkManager.getInstance(applicationContext!!)
            .beginWith(signalingSettingsWorker)
            .then(websocketConnectionsWorker)
            .enqueue()
    }

    private fun proceedWithLogin() {
        cookieManager.cookieStore.removeAll()

        if (userManager.users.blockingGet().size == 1 ||
            currentUserProviderOld.currentUser.blockingGet().id != internalAccountId
        ) {
            val userToSetAsActive = userManager.getUserWithId(internalAccountId).blockingGet()
            Log.d(TAG, "userToSetAsActive: " + userToSetAsActive.username)

            if (userManager.setUserAsActive(userToSetAsActive).blockingGet()) {
                runOnUiThread {
                    if (userManager.users.blockingGet().size == 1) {
                        val intent = Intent(context, ConversationsListActivity::class.java)
                        startActivity(intent)
                    } else {
                        if (isAccountImport) {
                            ApplicationWideMessageHolder.getInstance().messageType =
                                ApplicationWideMessageHolder.MessageType.ACCOUNT_WAS_IMPORTED
                        }
                        val intent = Intent(context, ConversationsListActivity::class.java)
                        startActivity(intent)
                    }
                }
            } else {
                Log.e(TAG, "failed to set active user")
                Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
            }
        } else {
            Log.d(TAG, "continuing proceedWithLogin was skipped for this user")
        }
    }

    private fun dispose() {
        for (i in disposables.indices) {
            if (!disposables[i].isDisposed) {
                disposables[i].dispose()
            }
        }
    }

    public override fun onDestroy() {
        dispose()
        super.onDestroy()
    }

    private fun abortVerification() {
        if (isAccountImport) {
            ApplicationWideMessageHolder.getInstance().messageType = ApplicationWideMessageHolder.MessageType
                .FAILED_TO_IMPORT_ACCOUNT
            runOnUiThread {
                Handler().postDelayed({
                    val intent = Intent(this, ServerSelectionActivity::class.java)
                    startActivity(intent)
                }, DELAY_IN_MILLIS)
            }
        } else {
            if (internalAccountId != -1L) {
                runOnUiThread {
                    deleteUserAndStartServerSelection(internalAccountId)
                }
            } else {
                runOnUiThread {
                    Handler().postDelayed({
                        val intent = Intent(this, ServerSelectionActivity::class.java)
                        startActivity(intent)
                    }, DELAY_IN_MILLIS)
                }
            }
        }
    }

    @SuppressLint("CheckResult")
    private fun deleteUserAndStartServerSelection(userId: Long) {
        userManager.scheduleUserForDeletionWithId(userId).blockingGet()
        val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
        WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)

        WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
            .observeForever { workInfo: WorkInfo? ->

                when (workInfo?.state) {
                    WorkInfo.State.SUCCEEDED -> {
                        val intent = Intent(this, ServerSelectionActivity::class.java)
                        startActivity(intent)
                    }

                    WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
                        Toast.makeText(
                            context,
                            context.resources.getString(R.string.nc_common_error_sorry),
                            Toast.LENGTH_LONG
                        ).show()
                        Log.e(TAG, "something went wrong when deleting user with id $userId")
                        val intent = Intent(this, ServerSelectionActivity::class.java)
                        startActivity(intent)
                    }

                    else -> {}
                }
            }
    }

    companion object {
        private val TAG = AccountVerificationActivity::class.java.simpleName
        const val DELAY_IN_MILLIS: Long = 7500
    }
}
