package world.respect.shared.viewmodel.manageuser.selectaccount

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import androidx.navigation.toRoute
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.koin.core.component.KoinScopeComponent
import org.koin.core.scope.Scope
import world.respect.datalayer.DataLoadParams
import world.respect.datalayer.SchoolDataSource
import world.respect.datalayer.ext.dataOrNull
import world.respect.datalayer.school.model.Person
import world.respect.datalayer.school.model.PersonGenderEnum
import world.respect.datalayer.school.model.PersonStatusEnum
import world.respect.libutil.ext.replaceOrAppend
import world.respect.shared.domain.account.RespectAccount
import world.respect.shared.domain.account.RespectAccountManager
import world.respect.shared.domain.account.RespectSession
import world.respect.shared.domain.account.RespectSessionAndPerson
import world.respect.shared.generated.resources.Res
import world.respect.shared.generated.resources.select_account
import world.respect.shared.navigation.AcceptInvite
import world.respect.shared.navigation.GetStartedScreen
import world.respect.shared.navigation.LoginScreen
import world.respect.shared.navigation.NavCommand
import world.respect.shared.navigation.SelectAccount
import world.respect.shared.navigation.WaitingForApproval
import world.respect.shared.util.ext.asUiText
import world.respect.shared.util.ext.isSameAccount
import world.respect.shared.viewmodel.RespectViewModel

/**
 * @property selectedAccount if not null, the currently selected account
 * @property accounts other accounts that are signed-in, available, and the user can switch to (
 *           (not including the selectedAccount)
 */
data class SelectAccountUiState(
    val accounts: List<RespectSessionAndPerson> = emptyList(),
)

class SelectAccountViewModel(
    private val respectAccountManager: RespectAccountManager,
    savedStateHandle: SavedStateHandle
) : RespectViewModel(savedStateHandle), KoinScopeComponent {
    override val scope: Scope = respectAccountManager.requireActiveAccountScope()

    private val route: SelectAccount = savedStateHandle.toRoute()

    private val _uiState = MutableStateFlow(SelectAccountUiState())


    val uiState = _uiState.asStateFlow()


    private var emittedNavToGetStartedCommand = false

    init {
        _appUiState.update {
            it.copy(
                title = Res.string.select_account.asUiText(),
                hideBottomNavigation = true,
                userAccountIconVisible = false,
            )
        }


        viewModelScope.launch {
            respectAccountManager.accounts.combine(
                respectAccountManager.selectedAccountFlow
            ) { storedAccounts, activeAccount ->
                Pair(storedAccounts, activeAccount)
            }.collectLatest { (storedAccounts, activeAccount) ->
                /*
                 * If there are no stored accounts (eg because they have logged out of all accounts),
                 * or if a session is terminated remotely (eg password reset), then must go to
                 * GetStarted screen.
                 */
                if (storedAccounts.isEmpty() && !emittedNavToGetStartedCommand) {
                    emittedNavToGetStartedCommand = true
                    _navCommandFlow.tryEmit(
                        NavCommand.Navigate(
                            GetStartedScreen(), clearBackStack = true
                        )
                    )

                    return@collectLatest
                }


                _uiState.update { prev ->
                    prev.copy(
                        accounts = storedAccounts.map {
                            RespectSessionAndPerson(
                                session = RespectSession(it, null),
                                person = Person(
                                    guid = it.userGuid,
                                    givenName = "",
                                    familyName = "",
                                    roles = emptyList(),
                                    gender = PersonGenderEnum.UNSPECIFIED,
                                )
                            )
                        }
                    )
                }

                storedAccounts.forEach { account ->
                    launch {
                        val accountScope = respectAccountManager.getOrCreateAccountScope(account)
                        val dataSource: SchoolDataSource = accountScope.get()
                        dataSource.personDataSource.findByGuidAsFlow(
                            account.userGuid
                        ).collect { person ->
                            _uiState.update { prev ->
                                prev.copy(
                                    accounts = prev.accounts.replaceOrAppend(
                                        RespectSessionAndPerson(
                                            session = RespectSession(account, null),
                                            person = person.dataOrNull() ?: Person(
                                                guid = account.userGuid,
                                                givenName = "",
                                                familyName = "",
                                                roles = emptyList(),
                                                gender = PersonGenderEnum.UNSPECIFIED,
                                            )
                                        )
                                    ) {
                                        it.session.account.isSameAccount(account)
                                    }
                                )
                            }
                        }
                    }
                }
            }
        }
    }

    fun onClickAccount(account: RespectAccount) {
        respectAccountManager.switchAccount(account)

        viewModelScope.launch {
            val accountScope = respectAccountManager.getOrCreateAccountScope(account)
            val person = accountScope.get<SchoolDataSource>().personDataSource.findByGuid(
                loadParams = DataLoadParams(onlyIfCached = true),
                guid = account.userGuid
            )

            _navCommandFlow.tryEmit(
                NavCommand.Navigate(
                    destination = if (person.dataOrNull()?.status != PersonStatusEnum.PENDING_APPROVAL) {
                        AcceptInvite.create(
                            schoolUrl = account.school.self,
                            code = route.inviteCode ?: "",
                            canGoBack = true,
                            guid = person.dataOrNull()?.guid
                        )
                    } else {
                        WaitingForApproval()
                    },
                    clearBackStack = true
                )
            )
        }


    }


    fun onClickAddAccount() {
        _navCommandFlow.tryEmit(
            NavCommand.Navigate(
                LoginScreen(
                    schoolUrlStr = respectAccountManager.activeAccount?.school?.self.toString(),
                    inviteCode = route.inviteCode
                )
            )
        )
    }


}