package com.tt.honeyDue.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tt.honeyDue.data.DataManager
import com.tt.honeyDue.data.IDataManager
import com.tt.honeyDue.models.NotificationPreference
import com.tt.honeyDue.models.UpdateNotificationPreferencesRequest
import com.tt.honeyDue.network.APILayer
import com.tt.honeyDue.network.ApiResult
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

/**
 * Stable channel-id list used by the per-category notification UI. Keep
 * in lockstep with the Android `NotificationChannels` object (the same
 * four ids) — the parity is enforced by
 * `NotificationPreferencesScreenTest.categoryKeys_matchNotificationChannels`.
 */
object NotificationCategoryKeys {
    const val TASK_REMINDER = "task_reminder"
    const val TASK_OVERDUE = "task_overdue"
    const val RESIDENCE_INVITE = "residence_invite"
    const val SUBSCRIPTION = "subscription"

    val ALL: List<String> = listOf(
        TASK_REMINDER,
        TASK_OVERDUE,
        RESIDENCE_INVITE,
        SUBSCRIPTION,
    )
}

/**
 * Platform-agnostic façade around the per-category notification preference
 * store. The Android implementation wires `loadAll` / `setCategory` /
 * `setAll` to `NotificationPreferencesStore` (DataStore-backed); tests in
 * `commonTest` wire them to an in-memory fake.
 *
 * Kept in the viewmodel package (not `ui.screens`) so it can be referenced
 * from `commonTest` without pulling in Compose types.
 */
class NotificationCategoriesController(
    private val loadAll: suspend () -> Map<String, Boolean>,
    private val setCategory: suspend (String, Boolean) -> Unit,
    private val setAll: suspend (Boolean) -> Unit,
) {
    suspend fun load(): Map<String, Boolean> = loadAll()

    suspend fun onCategoryToggle(channelId: String, enabled: Boolean) {
        setCategory(channelId, enabled)
    }

    suspend fun onMasterToggle(enabled: Boolean) {
        setAll(enabled)
    }

    companion object {
        /**
         * Compute the master toggle's visible state from the current
         * per-category snapshot. Master is "on" iff every category is on
         * AND the snapshot is non-empty (an empty snapshot is the
         * "no data yet" state, which the UI renders as master-off).
         */
        fun computeMasterState(snapshot: Map<String, Boolean>): Boolean =
            snapshot.isNotEmpty() && snapshot.values.all { it }
    }
}

/**
 * Existing remote-preferences ViewModel (server-backed). Retained
 * unchanged so the legacy Daily Digest / email prefs continue to work
 * alongside the new per-category local toggles driven by
 * [NotificationCategoriesController].
 */
class NotificationPreferencesViewModel(
    private val dataManager: IDataManager = DataManager,
) : ViewModel() {

    /** Server-backed preferences — derived from [IDataManager.notificationPreferences].
     *  APILayer.getNotificationPreferences / updateNotificationPreferences write through. */
    val preferencesState: StateFlow<ApiResult<NotificationPreference>> =
        dataManager.notificationPreferences
            .map { if (it != null) ApiResult.Success(it) else ApiResult.Idle }
            .stateIn(
                viewModelScope,
                SharingStarted.Eagerly,
                dataManager.notificationPreferences.value
                    ?.let { ApiResult.Success(it) } ?: ApiResult.Idle,
            )

    private val _updateState = MutableStateFlow<ApiResult<NotificationPreference>>(ApiResult.Idle)
    val updateState: StateFlow<ApiResult<NotificationPreference>> = _updateState.asStateFlow()

    /**
     * Per-category local toggle state, keyed by [NotificationCategoryKeys]
     * channel ids. Backed on Android by `NotificationPreferencesStore` via
     * [NotificationCategoriesController].
     */
    private val _categoryState = MutableStateFlow<Map<String, Boolean>>(
        NotificationCategoryKeys.ALL.associateWith { true },
    )
    val categoryState: StateFlow<Map<String, Boolean>> = _categoryState.asStateFlow()

    fun loadPreferences() {
        // Fire the API call; APILayer writes to DataManager.setNotificationPreferences
        // on success and [preferencesState] re-emits automatically.
        viewModelScope.launch { APILayer.getNotificationPreferences() }
    }

    fun updatePreference(
        taskDueSoon: Boolean? = null,
        taskOverdue: Boolean? = null,
        taskCompleted: Boolean? = null,
        taskAssigned: Boolean? = null,
        residenceShared: Boolean? = null,
        warrantyExpiring: Boolean? = null,
        dailyDigest: Boolean? = null,
        emailTaskCompleted: Boolean? = null,
        taskDueSoonHour: Int? = null,
        taskOverdueHour: Int? = null,
        warrantyExpiringHour: Int? = null,
        dailyDigestHour: Int? = null,
    ) {
        viewModelScope.launch {
            _updateState.value = ApiResult.Loading
            val request = UpdateNotificationPreferencesRequest(
                taskDueSoon = taskDueSoon,
                taskOverdue = taskOverdue,
                taskCompleted = taskCompleted,
                taskAssigned = taskAssigned,
                residenceShared = residenceShared,
                warrantyExpiring = warrantyExpiring,
                dailyDigest = dailyDigest,
                emailTaskCompleted = emailTaskCompleted,
                taskDueSoonHour = taskDueSoonHour,
                taskOverdueHour = taskOverdueHour,
                warrantyExpiringHour = warrantyExpiringHour,
                dailyDigestHour = dailyDigestHour,
            )
            // APILayer.updateNotificationPreferences writes through to
            // DataManager.setNotificationPreferences on success, so
            // [preferencesState] re-emits automatically.
            _updateState.value = APILayer.updateNotificationPreferences(request)
        }
    }

    fun resetUpdateState() {
        _updateState.value = ApiResult.Idle
    }

    // ---------------------------------------------------------------------
    // Per-category (local) toggle state — wired to platform-specific store.
    // ---------------------------------------------------------------------

    /**
     * Attach a [NotificationCategoriesController] and do an initial load
     * so the screen can render in sync with the on-disk preferences. Safe
     * to call multiple times; later calls replace the controller.
     */
    fun attachCategoriesController(controller: NotificationCategoriesController) {
        this.categoriesController = controller
        viewModelScope.launch {
            _categoryState.value = controller.load()
        }
    }

    fun toggleCategory(channelId: String, enabled: Boolean) {
        val controller = categoriesController ?: return
        // Optimistic local update so the Switch flips immediately.
        _categoryState.value = _categoryState.value.toMutableMap().apply {
            put(channelId, enabled)
        }
        viewModelScope.launch {
            controller.onCategoryToggle(channelId, enabled)
        }
    }

    fun toggleMaster(enabled: Boolean) {
        val controller = categoriesController ?: return
        _categoryState.value = NotificationCategoryKeys.ALL.associateWith { enabled }
        viewModelScope.launch {
            controller.onMasterToggle(enabled)
        }
    }

    private var categoriesController: NotificationCategoriesController? = null
}
