package com.fabledsword.minstrel.admin.ui

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.fabledsword.minstrel.admin.data.AdminTagSourcesRepository
import com.fabledsword.minstrel.api.ErrorCopy
import com.fabledsword.minstrel.connectivity.NetworkStatusController
import com.fabledsword.minstrel.connectivity.recoveries
import com.fabledsword.minstrel.models.AdminTagSourceRef
import com.fabledsword.minstrel.models.TagSourceTestResult
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

sealed interface AdminTagSourcesUiState {
    data object Loading : AdminTagSourcesUiState
    data object Empty : AdminTagSourcesUiState
    data class Success(
        val providers: List<AdminTagSourceRef>,
        val testResults: Map<String, TagSourceTestResult>,
    ) : AdminTagSourcesUiState
    data class Error(val message: String) : AdminTagSourcesUiState
}

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

    private val internal = MutableStateFlow<AdminTagSourcesUiState>(AdminTagSourcesUiState.Loading)
    val uiState: StateFlow<AdminTagSourcesUiState> = internal.asStateFlow()

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

    fun refresh(): Job = viewModelScope.launch {
        internal.value = AdminTagSourcesUiState.Loading
        try {
            val providers = repository.list()
            internal.value = if (providers.isEmpty()) {
                AdminTagSourcesUiState.Empty
            } else {
                AdminTagSourcesUiState.Success(providers, emptyMap())
            }
        } catch (
            @Suppress("TooGenericExceptionCaught") e: Throwable,
        ) {
            internal.value = AdminTagSourcesUiState.Error(ErrorCopy.fromThrowable(e))
        }
    }

    fun setEnabled(id: String, enabled: Boolean) {
        val before = internal.value as? AdminTagSourcesUiState.Success ?: return
        // Optimistic toggle; reconcile via refresh() if the server rejects.
        internal.value = before.copy(
            providers = before.providers.map {
                if (it.id == id) it.copy(enabled = enabled) else it
            },
        )
        viewModelScope.launch {
            try {
                repository.setEnabled(id, enabled)
            } catch (
                @Suppress("TooGenericExceptionCaught", "SwallowedException") e: Throwable,
            ) {
                refresh()
            }
        }
    }

    fun saveApiKey(id: String, apiKey: String) {
        viewModelScope.launch {
            try {
                replaceProvider(repository.setApiKey(id, apiKey))
            } catch (
                @Suppress("TooGenericExceptionCaught", "SwallowedException") e: Throwable,
            ) {
                refresh()
            }
        }
    }

    fun test(id: String) {
        viewModelScope.launch {
            val result = try {
                repository.test(id)
            } catch (
                @Suppress("TooGenericExceptionCaught") e: Throwable,
            ) {
                TagSourceTestResult(ok = false, error = ErrorCopy.fromThrowable(e))
            }
            val current = internal.value as? AdminTagSourcesUiState.Success ?: return@launch
            internal.value = current.copy(testResults = current.testResults + (id to result))
        }
    }

    private fun replaceProvider(updated: AdminTagSourceRef) {
        val current = internal.value as? AdminTagSourcesUiState.Success ?: return
        internal.value = current.copy(
            providers = current.providers.map { if (it.id == updated.id) updated else it },
        )
    }
}
