LoginOptionsPresenter.kt

package chat.rocket.android.authentication.loginoptions.presentation

import chat.rocket.android.analytics.AnalyticsManager
import chat.rocket.android.analytics.event.AuthenticationEvent
import chat.rocket.android.authentication.domain.model.DeepLinkInfo
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.infrastructure.LocalRepository
import chat.rocket.android.server.domain.GetConnectingServerInteractor
import chat.rocket.android.server.domain.GetSettingsInteractor
import chat.rocket.android.server.domain.PublicSettings
import chat.rocket.android.server.domain.SaveAccountInteractor
import chat.rocket.android.server.domain.SaveCurrentServerInteractor
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.favicon
import chat.rocket.android.server.domain.model.Account
import chat.rocket.android.server.domain.siteName
import chat.rocket.android.server.domain.wideTile
import chat.rocket.android.server.infrastructure.RocketChatClientFactory
import chat.rocket.android.util.extension.launchUI
import chat.rocket.android.util.extensions.avatarUrl
import chat.rocket.android.util.extensions.serverLogoUrl
import chat.rocket.android.util.retryIO
import chat.rocket.common.RocketChatAuthException
import chat.rocket.common.RocketChatException
import chat.rocket.common.model.Email
import chat.rocket.common.model.Token
import chat.rocket.common.model.User
import chat.rocket.common.util.ifNull
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.loginWithCas
import chat.rocket.core.internal.rest.loginWithOauth
import chat.rocket.core.internal.rest.loginWithSaml
import chat.rocket.core.internal.rest.me
import kotlinx.coroutines.delay
import testConfig.Config.Companion.DEFAULT_TEST_URL
import javax.inject.Inject

private const val TYPE_LOGIN_OAUTH = 1
private const val TYPE_LOGIN_CAS = 2
private const val TYPE_LOGIN_SAML = 3
private const val TYPE_LOGIN_DEEP_LINK = 4

class LoginOptionsPresenter @Inject constructor(
    private val view: LoginOptionsView,
    private val strategy: CancelStrategy,
    private val factory: RocketChatClientFactory,
    private val navigator: AuthenticationNavigator,
    private val settingsInteractor: GetSettingsInteractor,
    private val localRepository: LocalRepository,
    private val saveCurrentServer: SaveCurrentServerInteractor,
    private val saveAccountInteractor: SaveAccountInteractor,
    private val analyticsManager: AnalyticsManager,
    private val tokenRepository: TokenRepository,
    serverInteractor: GetConnectingServerInteractor
) {
    // TODO - we should validate the current server when opening the app, and have a nonnull get()
    private var currentServer = serverInteractor.get() ?: DEFAULT_TEST_URL
    private val token = tokenRepository.get(currentServer)
    private lateinit var client: RocketChatClient
    private lateinit var settings: PublicSettings
    private lateinit var credentialToken: String
    private lateinit var credentialSecret: String
    private lateinit var deepLinkUserId: String
    private lateinit var deepLinkToken: String
    private lateinit var loginMethod: AuthenticationEvent

    fun toCreateAccount() = navigator.toCreateAccount()

    fun toLoginWithEmail() = navigator.toLogin(currentServer)

    fun authenticateWithOauth(oauthToken: String, oauthSecret: String) {
        setupConnectionInfo(currentServer)
        credentialToken = oauthToken
        credentialSecret = oauthSecret
        loginMethod = AuthenticationEvent.AuthenticationWithOauth
        doAuthentication(TYPE_LOGIN_OAUTH)
    }

    fun authenticateWithCas(casToken: String) {
        setupConnectionInfo(currentServer)
        credentialToken = casToken
        loginMethod = AuthenticationEvent.AuthenticationWithCas
        doAuthentication(TYPE_LOGIN_CAS)
    }

    fun authenticateWithSaml(samlToken: String) {
        setupConnectionInfo(currentServer)
        credentialToken = samlToken
        loginMethod = AuthenticationEvent.AuthenticationWithSaml
        doAuthentication(TYPE_LOGIN_SAML)
    }

    fun authenticateWithDeepLink(deepLinkInfo: DeepLinkInfo) {
        val serverUrl = deepLinkInfo.url
        setupConnectionInfo(serverUrl)
        if (deepLinkInfo.userId != null && deepLinkInfo.token != null) {
            deepLinkUserId = deepLinkInfo.userId
            deepLinkToken = deepLinkInfo.token
            tokenRepository.save(serverUrl, Token(deepLinkUserId, deepLinkToken))
            loginMethod = AuthenticationEvent.AuthenticationWithDeeplink
            doAuthentication(TYPE_LOGIN_DEEP_LINK)
        }
    }

    private fun doAuthentication(loginType: Int) {
        launchUI(strategy) {
            view.showLoading()
            try {
                val token = retryIO("login") {
                    when (loginType) {
                        TYPE_LOGIN_OAUTH -> client.loginWithOauth(credentialToken, credentialSecret)
                        TYPE_LOGIN_CAS -> {
                            delay(3000)
                            client.loginWithCas(credentialToken)
                        }
                        TYPE_LOGIN_SAML -> {
                            delay(3000)
                            client.loginWithSaml(credentialToken)
                        }
                        TYPE_LOGIN_DEEP_LINK -> {
                            val myself = client.me() // Just checking if the credentials worked.
                            if (myself.id == deepLinkUserId) {
                                Token(deepLinkUserId, deepLinkToken)
                            } else {
                                throw RocketChatAuthException("Invalid AuthenticationEvent Deep Link Credentials...")
                            }
                        }
                        else -> {
                            throw IllegalStateException(
                                "Expected TYPE_LOGIN_USER_EMAIL, " +
                                        "TYPE_LOGIN_CAS,TYPE_LOGIN_SAML, TYPE_LOGIN_OAUTH or " +
                                        "TYPE_LOGIN_DEEP_LINK"
                            )
                        }
                    }
                }
                val myself = retryIO("me()") { client.me() }
                myself.username?.let { username ->
                    val user = User(
                        id = myself.id,
                        roles = myself.roles,
                        status = myself.status,
                        name = myself.name,
                        emails = myself.emails?.map { Email(it.address ?: "", it.verified) },
                        username = myself.username,
                        utcOffset = myself.utcOffset
                    )
                    localRepository.saveCurrentUser(url = currentServer, user = user)
                    saveCurrentServer.save(currentServer)
                    localRepository.save(LocalRepository.CURRENT_USERNAME_KEY, username)
                    saveAccount(username)
                    saveToken(token)
                    analyticsManager.logLogin(loginMethod, true)
                    navigator.toChatList()
                }.ifNull {
                    if (loginType == TYPE_LOGIN_OAUTH) {
                        navigator.toRegisterUsername(token.userId, token.authToken)
                    }
                }
            } catch (exception: RocketChatException) {
                analyticsManager.logLogin(loginMethod, false)
                exception.message?.let {
                    view.showMessage(it)
                }.ifNull {
                    view.showGenericErrorMessage()
                }
            } finally {
                view.hideLoading()
            }
        }
    }

    private fun setupConnectionInfo(serverUrl: String) {
        currentServer = serverUrl
        client = factory.get(currentServer)
        settings = settingsInteractor.get(currentServer)
    }

    private fun saveAccount(username: String) {
        val icon = settings.favicon()?.let {
            currentServer.serverLogoUrl(it)
        }
        val logo = settings.wideTile()?.let {
            currentServer.serverLogoUrl(it)
        }
        val thumb = currentServer.avatarUrl(username, token?.userId, token?.authToken)
        val account = Account(
            settings.siteName() ?: currentServer,
            currentServer,
            icon,
            logo,
            username,
            thumb
        )
        saveAccountInteractor.save(account)
    }

    private fun saveToken(token: Token) = tokenRepository.save(currentServer, token)
}