package com.tt.honeyDue.data

import com.tt.honeyDue.models.*
import com.tt.honeyDue.storage.TokenManager
import com.tt.honeyDue.storage.ThemeStorageManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlin.time.Clock
import kotlin.time.ExperimentalTime

/**
 * Unified DataManager - Single Source of Truth for all app data.
 *
 * Core Principles:
 * 1. All data is cached here - no other caches exist
 * 2. Every API response updates DataManager immediately
 * 3. All screens observe DataManager StateFlows directly
 * 4. Auth token and theme preferences are persisted via platform-specific managers
 *
 * Disk Persistence (survives app restart):
 *   - Current user, auth token (via TokenManager), theme (via ThemeStorageManager)
 *   - Lookup/reference data: categories, priorities, frequencies, specialties, residence types, task templates
 *   - ETag values for conditional fetching, onboarding completion flag
 *
 * In-memory only (re-fetched on app launch via prefetchAllData):
 *   - Residences, tasks, documents, contractors
 *   - Subscription status, summaries, upgrade triggers, feature benefits, promotions
 *
 * Data Flow:
 * User Action → API Call → Server Response → DataManager Updated → All Screens React
 */
object DataManager : IDataManager {

    // ==================== CACHE CONFIGURATION ====================

    /**
     * Default cache timeout in milliseconds.
     * Data older than this will be refreshed from the API.
     * Default: 1 hour (3600000ms)
     */
    const val CACHE_TIMEOUT_MS: Long = 60 * 60 * 1000L  // 1 hour

    /**
     * Per-entity cache TTLs for data with different freshness requirements.
     */
    object CacheTTL {
        /** Lookups (categories, priorities, frequencies) — rarely change */
        const val LOOKUPS_MS: Long = 24 * 60 * 60 * 1000L  // 24 hours
        /** Entity data (residences, tasks, documents, contractors) */
        const val ENTITIES_MS: Long = 60 * 60 * 1000L       // 1 hour
        /** Subscription status — needs frequent refresh */
        const val SUBSCRIPTION_MS: Long = 30 * 60 * 1000L   // 30 minutes
    }

    // Cache timestamps for each data type (epoch milliseconds)
    var residencesCacheTime: Long = 0L
        private set
    var myResidencesCacheTime: Long = 0L
        private set
    var tasksCacheTime: Long = 0L
        private set
    var tasksByResidenceCacheTime: MutableMap<Int, Long> = mutableMapOf()
        private set
    var contractorsCacheTime: Long = 0L
        private set
    var documentsCacheTime: Long = 0L
        private set
    var summaryCacheTime: Long = 0L
        private set

    /**
     * Check if cache for a given timestamp is still valid (not expired).
     * @param cacheTime Epoch milliseconds when the cache was last set.
     * @param ttlMs Optional TTL override. Defaults to CACHE_TIMEOUT_MS (1 hour).
     *              Use CacheTTL.LOOKUPS_MS for lookups, CacheTTL.SUBSCRIPTION_MS for subscription.
     */
    @OptIn(ExperimentalTime::class)
    fun isCacheValid(cacheTime: Long, ttlMs: Long = CACHE_TIMEOUT_MS): Boolean {
        if (cacheTime == 0L) return false
        val now = Clock.System.now().toEpochMilliseconds()
        return (now - cacheTime) < ttlMs
    }

    /**
     * Get current timestamp in milliseconds
     */
    @OptIn(ExperimentalTime::class)
    private fun currentTimeMs(): Long = Clock.System.now().toEpochMilliseconds()

    // Platform-specific persistence managers (initialized at app start)
    private var tokenManager: TokenManager? = null
    private var themeManager: ThemeStorageManager? = null
    private var persistenceManager: PersistenceManager? = null

    private val json = Json {
        ignoreUnknownKeys = true
        isLenient = true
        encodeDefaults = true
    }

    // ==================== AUTHENTICATION ====================

    private val _authToken = MutableStateFlow<String?>(null)
    val authToken: StateFlow<String?> = _authToken.asStateFlow()

    private val _currentUser = MutableStateFlow<User?>(null)
    override val currentUser: StateFlow<User?> = _currentUser.asStateFlow()

    // ==================== APP PREFERENCES ====================

    private val _themeId = MutableStateFlow("default")
    val themeId: StateFlow<String> = _themeId.asStateFlow()

    // ==================== ONBOARDING ====================

    private val _hasCompletedOnboarding = MutableStateFlow(false)
    val hasCompletedOnboarding: StateFlow<Boolean> = _hasCompletedOnboarding.asStateFlow()

    // ==================== RESIDENCES ====================

    private val _residences = MutableStateFlow<List<Residence>>(emptyList())
    override val residences: StateFlow<List<Residence>> = _residences.asStateFlow()

    private val _myResidences = MutableStateFlow<MyResidencesResponse?>(null)
    override val myResidences: StateFlow<MyResidencesResponse?> = _myResidences.asStateFlow()

    private val _totalSummary = MutableStateFlow<TotalSummary?>(null)
    override val totalSummary: StateFlow<TotalSummary?> = _totalSummary.asStateFlow()

    private val _residenceSummaries = MutableStateFlow<Map<Int, ResidenceSummaryResponse>>(emptyMap())
    override val residenceSummaries: StateFlow<Map<Int, ResidenceSummaryResponse>> = _residenceSummaries.asStateFlow()

    // ==================== TASKS ====================

    private val _allTasks = MutableStateFlow<TaskColumnsResponse?>(null)
    override val allTasks: StateFlow<TaskColumnsResponse?> = _allTasks.asStateFlow()

    private val _tasksByResidence = MutableStateFlow<Map<Int, TaskColumnsResponse>>(emptyMap())
    override val tasksByResidence: StateFlow<Map<Int, TaskColumnsResponse>> = _tasksByResidence.asStateFlow()

    private val _taskCompletions = MutableStateFlow<Map<Int, List<com.tt.honeyDue.models.TaskCompletionResponse>>>(emptyMap())
    override val taskCompletions: StateFlow<Map<Int, List<com.tt.honeyDue.models.TaskCompletionResponse>>> = _taskCompletions.asStateFlow()

    // ==================== DOCUMENTS ====================

    private val _documents = MutableStateFlow<List<Document>>(emptyList())
    override val documents: StateFlow<List<Document>> = _documents.asStateFlow()

    private val _documentsByResidence = MutableStateFlow<Map<Int, List<Document>>>(emptyMap())
    override val documentsByResidence: StateFlow<Map<Int, List<Document>>> = _documentsByResidence.asStateFlow()

    private val _documentDetail = MutableStateFlow<Map<Int, Document>>(emptyMap())
    override val documentDetail: StateFlow<Map<Int, Document>> = _documentDetail.asStateFlow()

    // ==================== CONTRACTORS ====================
    // Stores ContractorSummary for list views (lighter weight than full Contractor)

    private val _contractors = MutableStateFlow<List<ContractorSummary>>(emptyList())
    override val contractors: StateFlow<List<ContractorSummary>> = _contractors.asStateFlow()

    private val _contractorsByResidence = MutableStateFlow<Map<Int, List<ContractorSummary>>>(emptyMap())
    override val contractorsByResidence: StateFlow<Map<Int, List<ContractorSummary>>> = _contractorsByResidence.asStateFlow()

    private val _contractorDetail = MutableStateFlow<Map<Int, com.tt.honeyDue.models.Contractor>>(emptyMap())
    override val contractorDetail: StateFlow<Map<Int, com.tt.honeyDue.models.Contractor>> = _contractorDetail.asStateFlow()

    // ==================== SUBSCRIPTION ====================

    private val _subscription = MutableStateFlow<SubscriptionStatus?>(null)
    override val subscription: StateFlow<SubscriptionStatus?> = _subscription.asStateFlow()

    private val _upgradeTriggers = MutableStateFlow<Map<String, UpgradeTriggerData>>(emptyMap())
    override val upgradeTriggers: StateFlow<Map<String, UpgradeTriggerData>> = _upgradeTriggers.asStateFlow()

    private val _featureBenefits = MutableStateFlow<List<FeatureBenefit>>(emptyList())
    override val featureBenefits: StateFlow<List<FeatureBenefit>> = _featureBenefits.asStateFlow()

    private val _promotions = MutableStateFlow<List<Promotion>>(emptyList())
    override val promotions: StateFlow<List<Promotion>> = _promotions.asStateFlow()

    // ==================== LOOKUPS (Reference Data) ====================

    // List-based for dropdowns/pickers
    private val _residenceTypes = MutableStateFlow<List<ResidenceType>>(emptyList())
    override val residenceTypes: StateFlow<List<ResidenceType>> = _residenceTypes.asStateFlow()

    private val _taskFrequencies = MutableStateFlow<List<TaskFrequency>>(emptyList())
    override val taskFrequencies: StateFlow<List<TaskFrequency>> = _taskFrequencies.asStateFlow()

    private val _taskPriorities = MutableStateFlow<List<TaskPriority>>(emptyList())
    override val taskPriorities: StateFlow<List<TaskPriority>> = _taskPriorities.asStateFlow()

    private val _taskCategories = MutableStateFlow<List<TaskCategory>>(emptyList())
    override val taskCategories: StateFlow<List<TaskCategory>> = _taskCategories.asStateFlow()

    private val _contractorSpecialties = MutableStateFlow<List<ContractorSpecialty>>(emptyList())
    override val contractorSpecialties: StateFlow<List<ContractorSpecialty>> = _contractorSpecialties.asStateFlow()

    // ==================== TASK TEMPLATES ====================

    private val _taskTemplates = MutableStateFlow<List<TaskTemplate>>(emptyList())
    override val taskTemplates: StateFlow<List<TaskTemplate>> = _taskTemplates.asStateFlow()

    private val _taskTemplatesGrouped = MutableStateFlow<TaskTemplatesGroupedResponse?>(null)
    override val taskTemplatesGrouped: StateFlow<TaskTemplatesGroupedResponse?> = _taskTemplatesGrouped.asStateFlow()

    private val _notificationPreferences = MutableStateFlow<com.tt.honeyDue.models.NotificationPreference?>(null)
    override val notificationPreferences: StateFlow<com.tt.honeyDue.models.NotificationPreference?> = _notificationPreferences.asStateFlow()

    fun setNotificationPreferences(prefs: com.tt.honeyDue.models.NotificationPreference?) {
        _notificationPreferences.value = prefs
    }

    // Map-based for O(1) ID resolution
    private val _residenceTypesMap = MutableStateFlow<Map<Int, ResidenceType>>(emptyMap())
    val residenceTypesMap: StateFlow<Map<Int, ResidenceType>> = _residenceTypesMap.asStateFlow()

    private val _taskFrequenciesMap = MutableStateFlow<Map<Int, TaskFrequency>>(emptyMap())
    val taskFrequenciesMap: StateFlow<Map<Int, TaskFrequency>> = _taskFrequenciesMap.asStateFlow()

    private val _taskPrioritiesMap = MutableStateFlow<Map<Int, TaskPriority>>(emptyMap())
    val taskPrioritiesMap: StateFlow<Map<Int, TaskPriority>> = _taskPrioritiesMap.asStateFlow()

    private val _taskCategoriesMap = MutableStateFlow<Map<Int, TaskCategory>>(emptyMap())
    val taskCategoriesMap: StateFlow<Map<Int, TaskCategory>> = _taskCategoriesMap.asStateFlow()

    private val _contractorSpecialtiesMap = MutableStateFlow<Map<Int, ContractorSpecialty>>(emptyMap())
    val contractorSpecialtiesMap: StateFlow<Map<Int, ContractorSpecialty>> = _contractorSpecialtiesMap.asStateFlow()

    // ==================== STATE METADATA ====================

    private val _isInitialized = MutableStateFlow(false)
    val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()

    private val _lookupsInitialized = MutableStateFlow(false)
    val lookupsInitialized: StateFlow<Boolean> = _lookupsInitialized.asStateFlow()

    private val _lastSyncTime = MutableStateFlow(0L)
    val lastSyncTime: StateFlow<Long> = _lastSyncTime.asStateFlow()

    // ==================== SEEDED DATA ETAG ====================

    private val _seededDataETag = MutableStateFlow<String?>(null)
    val seededDataETag: StateFlow<String?> = _seededDataETag.asStateFlow()

    // ==================== INITIALIZATION ====================

    /**
     * Initialize DataManager with platform-specific managers.
     * Call this once at app startup.
     */
    fun initialize(
        tokenMgr: TokenManager,
        themeMgr: ThemeStorageManager,
        persistenceMgr: PersistenceManager
    ) {
        tokenManager = tokenMgr
        themeManager = themeMgr
        persistenceManager = persistenceMgr

        // Load auth token from secure storage
        _authToken.value = tokenMgr.getToken()

        // Load theme preference
        _themeId.value = themeMgr.getThemeId() ?: "default"

        // Load cached data from disk
        loadFromDisk()

        _isInitialized.value = true
    }

    /**
     * Check if user is authenticated (has valid token)
     */
    fun isAuthenticated(): Boolean = _authToken.value != null

    // ==================== O(1) LOOKUP HELPERS ====================

    override fun getResidenceType(id: Int?): ResidenceType? = id?.let { _residenceTypesMap.value[it] }
    override fun getTaskFrequency(id: Int?): TaskFrequency? = id?.let { _taskFrequenciesMap.value[it] }
    override fun getTaskPriority(id: Int?): TaskPriority? = id?.let { _taskPrioritiesMap.value[it] }
    override fun getTaskCategory(id: Int?): TaskCategory? = id?.let { _taskCategoriesMap.value[it] }
    override fun getContractorSpecialty(id: Int?): ContractorSpecialty? = id?.let { _contractorSpecialtiesMap.value[it] }

    // ==================== AUTH UPDATE METHODS ====================

    fun setAuthToken(token: String?) {
        _authToken.value = token
        if (token != null) {
            tokenManager?.saveToken(token)
        } else {
            tokenManager?.clearToken()
        }
    }

    fun setCurrentUser(user: User?) {
        _currentUser.value = user
        persistToDisk()
    }

    // ==================== THEME UPDATE METHODS ====================

    fun setThemeId(id: String) {
        _themeId.value = id
        themeManager?.saveThemeId(id)
    }

    // ==================== ONBOARDING UPDATE METHODS ====================

    fun setHasCompletedOnboarding(completed: Boolean) {
        _hasCompletedOnboarding.value = completed
        persistenceManager?.save(KEY_HAS_COMPLETED_ONBOARDING, completed.toString())
    }

    // ==================== RESIDENCE UPDATE METHODS ====================

    fun setResidences(residences: List<Residence>) {
        _residences.value = residences
        residencesCacheTime = currentTimeMs()
        updateLastSyncTime()
        persistToDisk()
    }

    fun setMyResidences(response: MyResidencesResponse) {
        _myResidences.value = response
        myResidencesCacheTime = currentTimeMs()
        summaryCacheTime = currentTimeMs()
        updateLastSyncTime()
        // Calculate summary from cached kanban data (API no longer returns summary stats)
        // This ensures summary is always up-to-date when residence view is shown
        refreshSummaryFromKanban()
        persistToDisk()
    }

    fun setTotalSummary(summary: TotalSummary) {
        _totalSummary.value = summary
        summaryCacheTime = currentTimeMs()
        persistToDisk()
    }

    /**
     * Calculate TotalSummary from cached kanban data.
     * This allows the client to compute summary stats without waiting for API responses
     * after CRUD operations (API now returns empty summaries for performance).
     *
     * Columns from API: overdue_tasks, in_progress_tasks, due_soon_tasks, upcoming_tasks, completed_tasks, cancelled_tasks
     */
    fun calculateSummaryFromKanban(): TotalSummary {
        val kanban = _allTasks.value ?: return _totalSummary.value ?: TotalSummary()
        val residenceCount = _myResidences.value?.residences?.size ?: _residences.value.size

        var overdueCount = 0
        var inProgressCount = 0
        var dueSoonCount = 0
        var upcomingCount = 0
        var completedCount = 0

        for (column in kanban.columns) {
            when (column.name) {
                "overdue_tasks" -> overdueCount = column.count
                "in_progress_tasks" -> inProgressCount = column.count
                "due_soon_tasks" -> dueSoonCount = column.count
                "upcoming_tasks" -> upcomingCount = column.count
                "completed_tasks" -> completedCount = column.count
                // cancelled_tasks is not counted in totals
            }
        }

        // totalTasks = all non-cancelled tasks
        val totalTasks = overdueCount + inProgressCount + dueSoonCount + upcomingCount + completedCount
        // totalPending = not completed (i.e., still needs attention)
        val totalPending = overdueCount + inProgressCount + dueSoonCount + upcomingCount

        return TotalSummary(
            totalResidences = residenceCount,
            totalTasks = totalTasks,
            totalPending = totalPending,
            totalOverdue = overdueCount,
            // due_soon_tasks column = tasks due within threshold (default 30 days)
            // upcoming_tasks column = tasks with dates beyond threshold OR no due date
            // Both "Due This Week" and "Next 30 Days" use due_soon since it represents the 30-day window
            tasksDueNextWeek = dueSoonCount,
            tasksDueNextMonth = dueSoonCount
        )
    }

    /**
     * Update totalSummary from cached kanban data.
     * Call this after task CRUD operations to keep summary in sync without API call.
     */
    fun refreshSummaryFromKanban() {
        val calculatedSummary = calculateSummaryFromKanban()
        _totalSummary.value = calculatedSummary
    }

    fun setResidenceSummary(residenceId: Int, summary: ResidenceSummaryResponse) {
        _residenceSummaries.value = _residenceSummaries.value + (residenceId to summary)
        persistToDisk()
    }

    /**
     * Add a new residence to the cache.
     * Caches affected: _residences, _myResidences
     * Invalidation trigger: createResidence API success
     */
    fun addResidence(residence: Residence) {
        _residences.value = _residences.value + residence
        // Also append to myResidences if it has been loaded
        _myResidences.value?.let { myRes ->
            _myResidences.value = myRes.copy(residences = myRes.residences + residence)
        }
        updateLastSyncTime()
        persistToDisk()
    }

    fun updateResidence(residence: Residence) {
        _residences.value = _residences.value.map {
            if (it.id == residence.id) residence else it
        }
        // Also update myResidences if present (used by getResidence cache lookup)
        _myResidences.value?.let { myRes ->
            val updatedResidences = myRes.residences.map {
                if (it.id == residence.id) residence else it
            }
            _myResidences.value = myRes.copy(residences = updatedResidences)
        }
        persistToDisk()
    }

    fun removeResidence(residenceId: Int) {
        _residences.value = _residences.value.filter { it.id != residenceId }
        _tasksByResidence.value = _tasksByResidence.value - residenceId
        _documentsByResidence.value = _documentsByResidence.value - residenceId
        _residenceSummaries.value = _residenceSummaries.value - residenceId

        // Also update myResidences so the list view updates immediately
        _myResidences.value?.let { current ->
            _myResidences.value = current.copy(
                residences = current.residences.filter { it.id != residenceId }
            )
        }
        persistToDisk()
    }

    // ==================== CACHE INVALIDATION ====================

    /** Invalidate the tasks cache so the next loadTasks() fetches fresh from API. */
    fun invalidateTasksCache() {
        tasksCacheTime = 0L
    }

    // ==================== TASK UPDATE METHODS ====================

    fun setAllTasks(response: TaskColumnsResponse) {
        _allTasks.value = response
        tasksCacheTime = currentTimeMs()
        updateLastSyncTime()
        // Refresh summary from kanban data (API no longer returns summary stats)
        refreshSummaryFromKanban()
        persistToDisk()
    }

    fun setTasksForResidence(residenceId: Int, response: TaskColumnsResponse) {
        _tasksByResidence.value = _tasksByResidence.value + (residenceId to response)
        tasksByResidenceCacheTime[residenceId] = currentTimeMs()
        persistToDisk()
    }

    /** Populate the per-task completion cache (used by TaskViewModel's derived flow). */
    fun setTaskCompletions(taskId: Int, completions: List<com.tt.honeyDue.models.TaskCompletionResponse>) {
        _taskCompletions.value = _taskCompletions.value + (taskId to completions)
    }

    /**
     * Filter cached allTasks by residence ID to avoid separate API call.
     * Returns null if allTasks not cached.
     * This enables client-side filtering when we already have all tasks loaded.
     */
    fun getTasksForResidence(residenceId: Int): TaskColumnsResponse? {
        val allTasksData = _allTasks.value ?: return null

        // Filter each column's tasks by residence ID
        val filteredColumns = allTasksData.columns.map { column ->
            column.copy(
                tasks = column.tasks.filter { it.residenceId == residenceId },
                count = column.tasks.count { it.residenceId == residenceId }
            )
        }

        return TaskColumnsResponse(
            columns = filteredColumns,
            daysThreshold = allTasksData.daysThreshold,
            residenceId = residenceId.toString()
        )
    }

    /**
     * Update a single task - moves it to the correct kanban column based on kanban_column field.
     * This is called after task completion, status change, etc.
     * Also refreshes the summary from the updated kanban data.
     */
    fun updateTask(task: TaskResponse) {
        // Update in allTasks
        _allTasks.value?.let { current ->
            val targetColumn = task.kanbanColumn ?: "upcoming_tasks"
            val newColumns = current.columns.map { column ->
                // Remove task from this column if present
                val filteredTasks = column.tasks.filter { it.id != task.id }
                // Add task if this is the target column
                val updatedTasks = if (column.name == targetColumn) {
                    filteredTasks + task
                } else {
                    filteredTasks
                }
                column.copy(tasks = updatedTasks, count = updatedTasks.size)
            }
            _allTasks.value = current.copy(columns = newColumns)
        }

        // Update in tasksByResidence if this task's residence is cached
        task.residenceId?.let { residenceId ->
            _tasksByResidence.value[residenceId]?.let { current ->
                val targetColumn = task.kanbanColumn ?: "upcoming_tasks"
                val newColumns = current.columns.map { column ->
                    val filteredTasks = column.tasks.filter { it.id != task.id }
                    val updatedTasks = if (column.name == targetColumn) {
                        filteredTasks + task
                    } else {
                        filteredTasks
                    }
                    column.copy(tasks = updatedTasks, count = updatedTasks.size)
                }
                _tasksByResidence.value = _tasksByResidence.value + (residenceId to current.copy(columns = newColumns))
            }
        }

        // Refresh summary from updated kanban data (API no longer returns summaries for CRUD)
        refreshSummaryFromKanban()
        persistToDisk()
    }

    fun removeTask(taskId: Int) {
        // Remove from allTasks
        _allTasks.value?.let { current ->
            val newColumns = current.columns.map { column ->
                val filteredTasks = column.tasks.filter { it.id != taskId }
                column.copy(tasks = filteredTasks, count = filteredTasks.size)
            }
            _allTasks.value = current.copy(columns = newColumns)
        }

        // Remove from all residence task caches
        _tasksByResidence.value = _tasksByResidence.value.mapValues { (_, tasks) ->
            val newColumns = tasks.columns.map { column ->
                val filteredTasks = column.tasks.filter { it.id != taskId }
                column.copy(tasks = filteredTasks, count = filteredTasks.size)
            }
            tasks.copy(columns = newColumns)
        }

        // Refresh summary from updated kanban data (API no longer returns summaries for CRUD)
        refreshSummaryFromKanban()
        persistToDisk()
    }

    // ==================== DOCUMENT UPDATE METHODS ====================

    fun setDocuments(documents: List<Document>) {
        _documents.value = documents
        documentsCacheTime = currentTimeMs()
        updateLastSyncTime()
        persistToDisk()
    }

    fun setDocumentsForResidence(residenceId: Int, documents: List<Document>) {
        _documentsByResidence.value = _documentsByResidence.value + (residenceId to documents)
        persistToDisk()
    }

    /** Populate the per-document detail cache (used by DocumentViewModel's derived flow). */
    fun setDocumentDetail(document: Document) {
        val id = document.id ?: return
        _documentDetail.value = _documentDetail.value + (id to document)
    }

    /**
     * Add a new document to the cache.
     * Caches affected: _documents, _documentsByResidence[residenceId]
     * Invalidation trigger: createDocument API success
     */
    fun addDocument(document: Document) {
        _documents.value = _documents.value + document
        // Also add to residence-specific cache if it exists
        val residenceId = document.residenceId ?: document.residence
        _documentsByResidence.value[residenceId]?.let { existing ->
            _documentsByResidence.value = _documentsByResidence.value + (residenceId to (existing + document))
        }
        persistToDisk()
    }

    /**
     * Update an existing document in the cache.
     * Caches affected: _documents, _documentsByResidence (all maps containing the document)
     * Invalidation trigger: updateDocument / uploadDocumentImage / deleteDocumentImage API success
     */
    fun updateDocument(document: Document) {
        _documents.value = _documents.value.map {
            if (it.id == document.id) document else it
        }
        // Also update in residence-specific caches
        _documentsByResidence.value = _documentsByResidence.value.mapValues { (_, docs) ->
            docs.map { if (it.id == document.id) document else it }
        }
        persistToDisk()
    }

    fun removeDocument(documentId: Int) {
        _documents.value = _documents.value.filter { it.id != documentId }
        // Also remove from residence-specific caches
        _documentsByResidence.value = _documentsByResidence.value.mapValues { (_, docs) ->
            docs.filter { it.id != documentId }
        }
        persistToDisk()
    }

    // ==================== CONTRACTOR UPDATE METHODS ====================

    fun setContractors(contractors: List<ContractorSummary>) {
        _contractors.value = contractors
        contractorsCacheTime = currentTimeMs()
        updateLastSyncTime()
        persistToDisk()
    }

    /** Populate the per-residence contractor cache. */
    fun setContractorsForResidence(residenceId: Int, contractors: List<ContractorSummary>) {
        _contractorsByResidence.value = _contractorsByResidence.value + (residenceId to contractors)
    }

    /** Populate the per-contractor detail cache (used by ContractorViewModel's derived flow). */
    fun setContractorDetail(contractor: com.tt.honeyDue.models.Contractor) {
        _contractorDetail.value = _contractorDetail.value + (contractor.id to contractor)
    }

    fun addContractor(contractor: ContractorSummary) {
        _contractors.value = _contractors.value + contractor
        persistToDisk()
    }

    /** Add a full Contractor (converts to summary for storage) */
    fun addContractor(contractor: Contractor) {
        _contractors.value = _contractors.value + contractor.toSummary()
        persistToDisk()
    }

    fun updateContractor(contractor: ContractorSummary) {
        _contractors.value = _contractors.value.map {
            if (it.id == contractor.id) contractor else it
        }
        persistToDisk()
    }

    /** Update from a full Contractor (converts to summary for storage) */
    fun updateContractor(contractor: Contractor) {
        val summary = contractor.toSummary()
        _contractors.value = _contractors.value.map {
            if (it.id == summary.id) summary else it
        }
        persistToDisk()
    }

    fun removeContractor(contractorId: Int) {
        _contractors.value = _contractors.value.filter { it.id != contractorId }
        persistToDisk()
    }

    // ==================== SUBSCRIPTION UPDATE METHODS ====================

    fun setSubscription(subscription: SubscriptionStatus?) {
        _subscription.value = subscription
        persistToDisk()
    }

    fun setUpgradeTriggers(triggers: Map<String, UpgradeTriggerData>) {
        _upgradeTriggers.value = triggers
        persistToDisk()
    }

    fun setFeatureBenefits(benefits: List<FeatureBenefit>) {
        _featureBenefits.value = benefits
        persistToDisk()
    }

    fun setPromotions(promos: List<Promotion>) {
        _promotions.value = promos
        persistToDisk()
    }

    // ==================== LOOKUP UPDATE METHODS ====================

    fun setResidenceTypes(types: List<ResidenceType>) {
        _residenceTypes.value = types
        _residenceTypesMap.value = types.associateBy { it.id }
        persistToDisk()
    }

    fun setTaskFrequencies(frequencies: List<TaskFrequency>) {
        _taskFrequencies.value = frequencies
        _taskFrequenciesMap.value = frequencies.associateBy { it.id }
        persistToDisk()
    }

    fun setTaskPriorities(priorities: List<TaskPriority>) {
        _taskPriorities.value = priorities
        _taskPrioritiesMap.value = priorities.associateBy { it.id }
        persistToDisk()
    }

    fun setTaskCategories(categories: List<TaskCategory>) {
        _taskCategories.value = categories
        _taskCategoriesMap.value = categories.associateBy { it.id }
        persistToDisk()
    }

    fun setContractorSpecialties(specialties: List<ContractorSpecialty>) {
        _contractorSpecialties.value = specialties
        _contractorSpecialtiesMap.value = specialties.associateBy { it.id }
        persistToDisk()
    }

    // ==================== TASK TEMPLATE UPDATE METHODS ====================

    fun setTaskTemplates(templates: List<TaskTemplate>) {
        _taskTemplates.value = templates
        // Don't persist - these are fetched fresh from API
    }

    fun setTaskTemplatesGrouped(response: TaskTemplatesGroupedResponse) {
        _taskTemplatesGrouped.value = response
        // Also extract flat list from grouped response
        val flatList = response.categories.flatMap { it.templates }
        _taskTemplates.value = flatList
        // Don't persist - these are fetched fresh from API
    }

    /**
     * Search task templates by query string (local search)
     */
    fun searchTaskTemplates(query: String): List<TaskTemplate> {
        if (query.length < 2) return emptyList()
        val lowercaseQuery = query.lowercase()
        return _taskTemplates.value.filter { template ->
            template.title.lowercase().contains(lowercaseQuery) ||
            template.description.lowercase().contains(lowercaseQuery) ||
            template.tags.any { it.lowercase().contains(lowercaseQuery) }
        }.take(10)
    }

    fun setAllLookups(staticData: StaticDataResponse) {
        setResidenceTypes(staticData.residenceTypes)
        setTaskFrequencies(staticData.taskFrequencies)
        setTaskPriorities(staticData.taskPriorities)
        setTaskCategories(staticData.taskCategories)
        setContractorSpecialties(staticData.contractorSpecialties)
        _lookupsInitialized.value = true
    }

    /**
     * Set all lookups from unified seeded data response.
     * Also stores the ETag for future conditional requests.
     * Persists lookup data to disk for faster app startup.
     */
    fun setAllLookupsFromSeededData(seededData: SeededDataResponse, etag: String?) {
        setResidenceTypes(seededData.residenceTypes)
        setTaskFrequencies(seededData.taskFrequencies)
        setTaskPriorities(seededData.taskPriorities)
        setTaskCategories(seededData.taskCategories)
        setContractorSpecialties(seededData.contractorSpecialties)
        setTaskTemplatesGrouped(seededData.taskTemplates)
        setSeededDataETag(etag)
        _lookupsInitialized.value = true
        // Persist lookups to disk for faster startup
        persistLookupsToDisk()
    }

    /**
     * Set the ETag for seeded data. Used for conditional requests.
     */
    fun setSeededDataETag(etag: String?) {
        _seededDataETag.value = etag
        if (etag != null) {
            persistenceManager?.save(KEY_SEEDED_DATA_ETAG, etag)
        } else {
            persistenceManager?.remove(KEY_SEEDED_DATA_ETAG)
        }
    }

    fun markLookupsInitialized() {
        _lookupsInitialized.value = true
    }

    // ==================== CLEAR METHODS ====================

    /**
     * Clear all data - called on logout
     */
    fun clear() {
        // Clear auth
        _authToken.value = null
        _currentUser.value = null
        tokenManager?.clearToken()

        // Clear user data
        _residences.value = emptyList()
        _myResidences.value = null
        _totalSummary.value = null
        _residenceSummaries.value = emptyMap()
        _allTasks.value = null
        _tasksByResidence.value = emptyMap()
        _documents.value = emptyList()
        _documentsByResidence.value = emptyMap()
        _documentDetail.value = emptyMap()
        _contractors.value = emptyList()
        _contractorsByResidence.value = emptyMap()
        _contractorDetail.value = emptyMap()
        _taskCompletions.value = emptyMap()
        _notificationPreferences.value = null

        // Clear subscription
        _subscription.value = null
        _upgradeTriggers.value = emptyMap()
        _featureBenefits.value = emptyList()
        _promotions.value = emptyList()

        // Clear lookups
        _residenceTypes.value = emptyList()
        _residenceTypesMap.value = emptyMap()
        _taskFrequencies.value = emptyList()
        _taskFrequenciesMap.value = emptyMap()
        _taskPriorities.value = emptyList()
        _taskPrioritiesMap.value = emptyMap()
        _taskCategories.value = emptyList()
        _taskCategoriesMap.value = emptyMap()
        _contractorSpecialties.value = emptyList()
        _contractorSpecialtiesMap.value = emptyMap()
        _taskTemplates.value = emptyList()
        _taskTemplatesGrouped.value = null
        _lookupsInitialized.value = false
        _seededDataETag.value = null

        // Clear cache timestamps
        residencesCacheTime = 0L
        myResidencesCacheTime = 0L
        tasksCacheTime = 0L
        tasksByResidenceCacheTime.clear()
        contractorsCacheTime = 0L
        documentsCacheTime = 0L
        summaryCacheTime = 0L

        // Clear metadata
        _lastSyncTime.value = 0L

        // Clear persistent storage (except theme)
        persistenceManager?.clear()
    }

    /**
     * Clear only user-specific data (keep lookups and preferences)
     */
    fun clearUserData() {
        _currentUser.value = null
        _residences.value = emptyList()
        _myResidences.value = null
        _totalSummary.value = null
        _residenceSummaries.value = emptyMap()
        _allTasks.value = null
        _tasksByResidence.value = emptyMap()
        _documents.value = emptyList()
        _documentsByResidence.value = emptyMap()
        _contractors.value = emptyList()
        _subscription.value = null
        _upgradeTriggers.value = emptyMap()
        _featureBenefits.value = emptyList()
        _promotions.value = emptyList()

        // Clear cache timestamps
        residencesCacheTime = 0L
        myResidencesCacheTime = 0L
        tasksCacheTime = 0L
        tasksByResidenceCacheTime.clear()
        contractorsCacheTime = 0L
        documentsCacheTime = 0L
        summaryCacheTime = 0L

        persistToDisk()
    }

    // ==================== PERSISTENCE ====================

    @OptIn(ExperimentalTime::class)
    private fun updateLastSyncTime() {
        _lastSyncTime.value = Clock.System.now().toEpochMilliseconds()
    }

    /**
     * Persist current state to disk.
     * Persists user data and lookup data for faster app startup.
     */
    private fun persistToDisk() {
        val manager = persistenceManager ?: return

        try {
            _currentUser.value?.let {
                manager.save(KEY_CURRENT_USER, json.encodeToString(it))
            }
        } catch (e: Exception) {
            println("DataManager: Error persisting user to disk: ${e.message}")
        }
    }

    /**
     * Persist lookup data to disk separately (called less frequently).
     */
    private fun persistLookupsToDisk() {
        val manager = persistenceManager ?: return

        try {
            if (_residenceTypes.value.isNotEmpty()) {
                manager.save(KEY_RESIDENCE_TYPES, json.encodeToString(_residenceTypes.value))
            }
            if (_taskFrequencies.value.isNotEmpty()) {
                manager.save(KEY_TASK_FREQUENCIES, json.encodeToString(_taskFrequencies.value))
            }
            if (_taskPriorities.value.isNotEmpty()) {
                manager.save(KEY_TASK_PRIORITIES, json.encodeToString(_taskPriorities.value))
            }
            if (_taskCategories.value.isNotEmpty()) {
                manager.save(KEY_TASK_CATEGORIES, json.encodeToString(_taskCategories.value))
            }
            if (_contractorSpecialties.value.isNotEmpty()) {
                manager.save(KEY_CONTRACTOR_SPECIALTIES, json.encodeToString(_contractorSpecialties.value))
            }
            _taskTemplatesGrouped.value?.let {
                manager.save(KEY_TASK_TEMPLATES_GROUPED, json.encodeToString(it))
            }
            println("DataManager: Lookup data persisted to disk")
        } catch (e: Exception) {
            println("DataManager: Error persisting lookups to disk: ${e.message}")
        }
    }

    /**
     * Load cached state from disk.
     * Loads user data and lookup data for faster app startup.
     */
    private fun loadFromDisk() {
        val manager = persistenceManager ?: return

        try {
            // Load user data
            manager.load(KEY_CURRENT_USER)?.let { data ->
                _currentUser.value = json.decodeFromString<User>(data)
            }

            // Load onboarding completion flag
            manager.load(KEY_HAS_COMPLETED_ONBOARDING)?.let { data ->
                _hasCompletedOnboarding.value = data.toBooleanStrictOrNull() ?: false
            }

            // Load seeded data ETag for conditional requests
            manager.load(KEY_SEEDED_DATA_ETAG)?.let { data ->
                _seededDataETag.value = data
            }

            // Load lookup data
            loadLookupsFromDisk()
        } catch (e: Exception) {
            println("DataManager: Error loading from disk: ${e.message}")
        }
    }

    /**
     * Load lookup data from disk.
     * If lookups are successfully loaded, mark as initialized.
     */
    private fun loadLookupsFromDisk() {
        val manager = persistenceManager ?: return
        var lookupsLoaded = false

        try {
            manager.load(KEY_RESIDENCE_TYPES)?.let { data ->
                val types = json.decodeFromString<List<ResidenceType>>(data)
                _residenceTypes.value = types
                _residenceTypesMap.value = types.associateBy { it.id }
                lookupsLoaded = true
            }

            manager.load(KEY_TASK_FREQUENCIES)?.let { data ->
                val frequencies = json.decodeFromString<List<TaskFrequency>>(data)
                _taskFrequencies.value = frequencies
                _taskFrequenciesMap.value = frequencies.associateBy { it.id }
            }

            manager.load(KEY_TASK_PRIORITIES)?.let { data ->
                val priorities = json.decodeFromString<List<TaskPriority>>(data)
                _taskPriorities.value = priorities
                _taskPrioritiesMap.value = priorities.associateBy { it.id }
            }

            manager.load(KEY_TASK_CATEGORIES)?.let { data ->
                val categories = json.decodeFromString<List<TaskCategory>>(data)
                _taskCategories.value = categories
                _taskCategoriesMap.value = categories.associateBy { it.id }
            }

            manager.load(KEY_CONTRACTOR_SPECIALTIES)?.let { data ->
                val specialties = json.decodeFromString<List<ContractorSpecialty>>(data)
                _contractorSpecialties.value = specialties
                _contractorSpecialtiesMap.value = specialties.associateBy { it.id }
            }

            manager.load(KEY_TASK_TEMPLATES_GROUPED)?.let { data ->
                val grouped = json.decodeFromString<TaskTemplatesGroupedResponse>(data)
                _taskTemplatesGrouped.value = grouped
                _taskTemplates.value = grouped.categories.flatMap { it.templates }
            }

            // Mark lookups as initialized if we loaded any data
            if (lookupsLoaded) {
                _lookupsInitialized.value = true
                println("DataManager: Lookup data loaded from disk")
            }
        } catch (e: Exception) {
            println("DataManager: Error loading lookups from disk: ${e.message}")
        }
    }

    // ==================== PERSISTENCE KEYS ====================

    private const val KEY_CURRENT_USER = "dm_current_user"
    private const val KEY_HAS_COMPLETED_ONBOARDING = "dm_has_completed_onboarding"
    private const val KEY_SEEDED_DATA_ETAG = "dm_seeded_data_etag"
    private const val KEY_RESIDENCE_TYPES = "dm_residence_types"
    private const val KEY_TASK_FREQUENCIES = "dm_task_frequencies"
    private const val KEY_TASK_PRIORITIES = "dm_task_priorities"
    private const val KEY_TASK_CATEGORIES = "dm_task_categories"
    private const val KEY_CONTRACTOR_SPECIALTIES = "dm_contractor_specialties"
    private const val KEY_TASK_TEMPLATES_GROUPED = "dm_task_templates_grouped"
}
