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

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.core.result.map
import com.runicgateway.app.data.api.dto.RosterCharDto
import com.runicgateway.app.data.repository.PlayerShardRepository
import com.runicgateway.app.data.repository.SettingsRepository
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

/**
 * The player's linked game accounts + per-account character rosters (PLAN.md §6.3):
 * link a game account with a `[link` one-time code (or, when the shard offers it,
 * a hybrid signup), then browse characters and open a text-only sheet. A per-account
 * roster carries its own load state so a down shard degrades that account to a
 * retry without blocking the rest (§7).
 */
@HiltViewModel
class CharactersViewModel @Inject constructor(
    private val repository: PlayerShardRepository,
    private val settingsRepository: SettingsRepository,
) : ViewModel() {

    /** A one-shot banner for the link / create-account forms. */
    data class Feedback(val ok: Boolean, @param:StringRes val messageRes: Int)

    data class State(
        val accounts: UiState<List<String>> = UiState.Loading,
        val rosters: Map<String, UiState<List<RosterCharDto>>> = emptyMap(),
        /** Whether the shard currently offers hybrid game-account signup. */
        val signupEnabled: Boolean = false,
        val busy: Boolean = false,
        val feedback: Feedback? = null,
    )

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

    init {
        load()
    }

    fun load() {
        _state.update { it.copy(accounts = UiState.Loading, rosters = emptyMap()) }
        viewModelScope.launch {
            when (val result = repository.accounts()) {
                is ApiResult.Ok -> {
                    val names = result.data.map { it.account }
                    _state.update { it.copy(accounts = UiState.Success(names)) }
                    names.forEach { loadRoster(it) }
                }
                else -> _state.update { it.copy(accounts = result.map { emptyList<String>() }.toUiState()) }
            }
        }
        // Non-critical: whether hybrid signup is offered right now.
        viewModelScope.launch {
            when (val settings = settingsRepository.getSettings()) {
                is ApiResult.Ok -> _state.update { it.copy(signupEnabled = settings.data.gameAccountSignup) }
                else -> Unit
            }
        }
    }

    fun loadRoster(account: String) {
        _state.update { it.copy(rosters = it.rosters + (account to UiState.Loading)) }
        viewModelScope.launch {
            val roster = repository.roster(account).map { it.chars }.toUiState()
            _state.update { it.copy(rosters = it.rosters + (account to roster)) }
        }
    }

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

    fun link(code: String) {
        if (_state.value.busy || code.isBlank()) return
        _state.update { it.copy(busy = true, feedback = null) }
        viewModelScope.launch {
            when (val result = repository.link(code.trim())) {
                is ApiResult.Ok -> {
                    _state.update { it.copy(busy = false, feedback = Feedback(true, R.string.player_link_ok)) }
                    load()
                }
                is ApiResult.HttpError -> _state.update {
                    it.copy(busy = false, feedback = Feedback(false, linkErrorRes(result.status)))
                }
                is ApiResult.NetworkError -> _state.update {
                    it.copy(busy = false, feedback = Feedback(false, R.string.error_network))
                }
            }
        }
    }

    fun createAccount(account: String, password: String) {
        if (_state.value.busy || account.isBlank() || password.length < 8) return
        _state.update { it.copy(busy = true, feedback = null) }
        viewModelScope.launch {
            when (val result = repository.createAccount(account.trim(), password)) {
                is ApiResult.Ok -> {
                    _state.update { it.copy(busy = false, feedback = Feedback(true, R.string.player_create_ok)) }
                    load()
                }
                is ApiResult.HttpError -> _state.update {
                    it.copy(busy = false, feedback = Feedback(false, createErrorRes(result.status)))
                }
                is ApiResult.NetworkError -> _state.update {
                    it.copy(busy = false, feedback = Feedback(false, R.string.error_network))
                }
            }
        }
    }

    private fun linkErrorRes(status: Int): Int = when (status) {
        400 -> R.string.player_link_bad_code
        503 -> R.string.error_shard_offline
        else -> R.string.player_link_error
    }

    private fun createErrorRes(status: Int): Int = when (status) {
        409 -> R.string.player_create_taken
        429 -> R.string.player_create_capped
        403 -> R.string.player_create_unavailable
        400 -> R.string.player_create_rejected
        503 -> R.string.error_shard_offline
        else -> R.string.player_create_error
    }
}
