package com.fabledsword.minstrel.admin.ui

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.fabledsword.minstrel.admin.data.AdminInvitesRepository
import com.fabledsword.minstrel.api.ErrorCopy
import com.fabledsword.minstrel.connectivity.NetworkStatusController
import com.fabledsword.minstrel.connectivity.recoveries
import com.fabledsword.minstrel.models.Invite
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

data class AdminInvitesUiState(
    val isLoading: Boolean = true,
    val invites: List<Invite> = emptyList(),
    val message: String? = null,
)

@HiltViewModel
class AdminInvitesViewModel @Inject constructor(
    private val repository: AdminInvitesRepository,
    networkStatus: NetworkStatusController,
) : ViewModel() {

    private val internal = MutableStateFlow(AdminInvitesUiState())
    val state: StateFlow<AdminInvitesUiState> = internal.asStateFlow()

    /** One-shot signal for screens to show the generated token + copy UI. */
    private val createdChannel = Channel<Invite>(Channel.BUFFERED)
    val created: Flow<Invite> = createdChannel.receiveAsFlow()

    init {
        refresh()
        // Screen-level auto-recovery (issue #1245): reload a failed list
        // when server health returns instead of waiting for a manual pull.
        viewModelScope.launch {
            networkStatus.recoveries().collect {
                if (internal.value.message != null) refresh()
            }
        }
    }

    fun refresh() {
        viewModelScope.launch {
            internal.update { it.copy(isLoading = true, message = null) }
            try {
                val rows = repository.list()
                internal.update { it.copy(isLoading = false, invites = rows) }
            } catch (
                @Suppress("TooGenericExceptionCaught") e: Throwable,
            ) {
                internal.update {
                    it.copy(
                        isLoading = false,
                        message = ErrorCopy.fromThrowable(e),
                    )
                }
            }
        }
    }

    fun generate(note: String?) {
        viewModelScope.launch {
            try {
                val invite = repository.create(note)
                createdChannel.trySend(invite)
                // Optimistically prepend; next refresh reconciles.
                internal.update { it.copy(invites = listOf(invite) + it.invites) }
            } catch (
                @Suppress("TooGenericExceptionCaught") e: Throwable,
            ) {
                internal.update {
                    it.copy(message = ErrorCopy.fromThrowable(e))
                }
            }
        }
    }

    fun revoke(token: String) {
        val before = internal.value.invites
        // Optimistic removal.
        internal.update { it.copy(invites = before.filterNot { i -> i.token == token }) }
        viewModelScope.launch {
            runCatching { repository.revoke(token) }
                .onFailure { refresh() }
        }
    }
}
