/*
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
package com.runicgateway.app.ui.auth

import androidx.annotation.StringRes
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.runicgateway.app.R
import com.runicgateway.app.core.result.ApiResult
import com.runicgateway.app.data.api.dto.LinkedIdentityDto
import com.runicgateway.app.data.api.dto.PlayerAccountDto
import com.runicgateway.app.data.api.dto.TotpSetupDto
import com.runicgateway.app.data.repository.AccountRepository
import com.runicgateway.app.data.repository.AuthRepository
import com.runicgateway.app.ui.UiState
import com.runicgateway.app.ui.toUiState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

/**
 * Drives the signed-in self-service surface (PLAN.md §6.3, §6.4) over
 * `/auth/me/account*`: change username/password, enroll/disable TOTP, and manage
 * linked SSO identities. Each mutation folds its [ApiResult] into a section-scoped
 * [Feedback] so the screen shows friendly, localized copy inline (§7). A username
 * change also re-validates the session so the shell reflects the new name at once.
 */
@HiltViewModel
class AccountViewModel @Inject constructor(
    private val accountRepository: AccountRepository,
    private val authRepository: AuthRepository,
) : ViewModel() {

    /** Which section an action's [Feedback] belongs to, so it renders in place. */
    enum class Section { USERNAME, PASSWORD, TOTP, IDENTITY }

    /** A one-shot result banner under a section. */
    data class Feedback(val section: Section, val ok: Boolean, @param:StringRes val messageRes: Int)

    data class State(
        val account: UiState<PlayerAccountDto> = UiState.Loading,
        val identities: List<LinkedIdentityDto> = emptyList(),
        /** True while any mutation is in flight (disables that section's controls). */
        val busy: Boolean = false,
        /** The pending TOTP enrollment (QR shown) between setup and enable. */
        val totpSetup: TotpSetupDto? = null,
        /** The single-use recovery codes returned once when 2FA was just enabled. */
        val recoveryCodesOnce: List<String>? = null,
        val feedback: Feedback? = null,
    )

    private val _state = MutableStateFlow(State())
    val state: StateFlow<State> = _state.asStateFlow()

    init {
        load()
    }

    fun load() {
        _state.update { it.copy(account = UiState.Loading) }
        viewModelScope.launch {
            val account = accountRepository.getAccount()
            _state.update { it.copy(account = account.toUiState()) }
            // Identities are non-critical — an empty list on failure is fine.
            when (val ids = accountRepository.identities()) {
                is ApiResult.Ok -> _state.update { it.copy(identities = ids.data) }
                else -> Unit
            }
        }
    }

    fun clearFeedback() = _state.update { it.copy(feedback = null) }

    // ── Username ─────────────────────────────────────────────────────────
    fun changeUsername(username: String) {
        if (_state.value.busy) return
        _state.update { it.copy(busy = true, feedback = null) }
        viewModelScope.launch {
            when (val result = accountRepository.changeUsername(username.trim())) {
                is ApiResult.Ok -> {
                    finish(Section.USERNAME, true, R.string.account_username_changed)
                    // Reflect the new name in the shell + refresh the loaded account.
                    authRepository.revalidate()
                    reloadAccount()
                }
                else -> finish(Section.USERNAME, false, usernameErrorRes(result))
            }
        }
    }

    // ── Password ─────────────────────────────────────────────────────────
    fun changePassword(newPassword: String, currentPassword: String?) {
        if (_state.value.busy) return
        _state.update { it.copy(busy = true, feedback = null) }
        viewModelScope.launch {
            when (accountRepository.changePassword(newPassword, currentPassword?.takeIf { it.isNotBlank() })) {
                is ApiResult.Ok -> finish(Section.PASSWORD, true, R.string.account_password_changed)
                is ApiResult.HttpError -> finish(Section.PASSWORD, false, R.string.account_password_error)
                is ApiResult.NetworkError -> finish(Section.PASSWORD, false, R.string.error_network)
            }
        }
    }

    // ── TOTP ─────────────────────────────────────────────────────────────
    fun beginTotp() {
        if (_state.value.busy) return
        _state.update { it.copy(busy = true, feedback = null) }
        viewModelScope.launch {
            when (val result = accountRepository.totpSetup()) {
                is ApiResult.Ok -> _state.update { it.copy(busy = false, totpSetup = result.data) }
                else -> finish(Section.TOTP, false, R.string.account_totp_setup_error)
            }
        }
    }

    fun cancelTotp() = _state.update { it.copy(totpSetup = null, feedback = null) }

    fun enableTotp(code: String) {
        if (_state.value.busy) return
        _state.update { it.copy(busy = true, feedback = null) }
        viewModelScope.launch {
            when (val result = accountRepository.totpEnable(code.trim())) {
                is ApiResult.Ok -> {
                    // 2FA enable returns the fresh recovery-code batch once — surface it.
                    _state.update {
                        it.copy(totpSetup = null, recoveryCodesOnce = result.data.recoveryCodes?.takeIf(List<String>::isNotEmpty))
                    }
                    finish(Section.TOTP, true, R.string.account_totp_enabled)
                    reloadAccount()
                }
                is ApiResult.HttpError -> finish(Section.TOTP, false, R.string.account_totp_code_error)
                is ApiResult.NetworkError -> finish(Section.TOTP, false, R.string.error_network)
            }
        }
    }

    /** Dismiss the one-time recovery-code batch shown after enabling 2FA. */
    fun dismissRecoveryCodes() = _state.update { it.copy(recoveryCodesOnce = null) }

    fun disableTotp(code: String) {
        if (_state.value.busy) return
        _state.update { it.copy(busy = true, feedback = null) }
        viewModelScope.launch {
            when (accountRepository.totpDisable(code.trim())) {
                is ApiResult.Ok -> {
                    finish(Section.TOTP, true, R.string.account_totp_disabled)
                    reloadAccount()
                }
                is ApiResult.HttpError -> finish(Section.TOTP, false, R.string.account_totp_code_error)
                is ApiResult.NetworkError -> finish(Section.TOTP, false, R.string.error_network)
            }
        }
    }

    // ── Identities ───────────────────────────────────────────────────────
    fun unlinkIdentity(provider: String) {
        if (_state.value.busy) return
        _state.update { it.copy(busy = true, feedback = null) }
        viewModelScope.launch {
            when (accountRepository.unlinkIdentity(provider)) {
                is ApiResult.Ok -> {
                    finish(Section.IDENTITY, true, R.string.account_identity_unlinked)
                    when (val ids = accountRepository.identities()) {
                        is ApiResult.Ok -> _state.update { it.copy(identities = ids.data) }
                        else -> Unit
                    }
                }
                else -> finish(Section.IDENTITY, false, R.string.account_identity_error)
            }
        }
    }

    private suspend fun reloadAccount() {
        when (val account = accountRepository.getAccount()) {
            is ApiResult.Ok -> _state.update { it.copy(account = UiState.Success(account.data)) }
            else -> Unit
        }
    }

    private fun finish(section: Section, ok: Boolean, @StringRes messageRes: Int) =
        _state.update { it.copy(busy = false, feedback = Feedback(section, ok, messageRes)) }

    private fun usernameErrorRes(result: ApiResult<*>): Int = when {
        result is ApiResult.HttpError && result.status == 409 -> R.string.account_username_taken
        result is ApiResult.NetworkError -> R.string.error_network
        else -> R.string.account_username_error
    }
}
