import Foundation
import SwiftUI

@Observable
final class ConversationListVM {
    var conversations: [Conversation] = []
    var invitations: [Invitation] = []
    var onlineUsers: Set<String> = []
    var unreadCounts: [String: Int] = [:]
    var favorites: Set<String> = []
    var avatarCache: [String: Data] = [:]  // convId -> avatar image data
    var isLoading = false

    private var notificationTask: Task<Void, Never>?
    private var avatarTask: Task<Void, Never>?
    private var refreshTask: Task<Void, Never>?
    private var localKey: Data?
    private var email: String = ""
    private var lastRefreshTime: Date = .distantPast

    func load(chatClient: ChatClient, email: String) async {
        isLoading = true
        self.email = email

        // Load favorites from disk (encrypted with localKey)
        localKey = await chatClient.localKey
        favorites = KeyStorage.loadFavorites(email: email, localKey: localKey)

        let currentUserId = await chatClient.userId ?? ""

        // Load cached conversations immediately (show while fetching from server)
        if let cached = MessageCache.loadConversations(email: email, cacheKey: localKey) {
            conversations = sortConversations(cached, currentUserId: currentUserId)
            for conv in conversations where conv.unreadCount > 0 {
                unreadCounts[conv.id] = conv.unreadCount
            }
        }

        // Load cached avatars from disk
        let diskAvatars = MessageCache.loadAllAvatars(email: email, cacheKey: localKey)
        if !diskAvatars.isEmpty {
            avatarCache = diskAvatars
        }

        // Fetch conversations from server
        let convs = await chatClient.listConversations()
        if !convs.isEmpty {
            // Sync unread counts from server (authoritative source)
            for conv in convs {
                unreadCounts[conv.id] = conv.unreadCount
            }
            conversations = sortConversations(convs, currentUserId: currentUserId)
            // Save to cache
            MessageCache.saveConversations(email: email, conversations: convs, cacheKey: localKey)
        }

        // Fetch invitations
        invitations = await chatClient.listInvitations()

        isLoading = false
        lastRefreshTime = Date()

        // Start notification listener
        startNotificationListener(chatClient: chatClient, email: email)

        // Read initial online users stored in ChatClient
        // (online_users notification arrives during login before any subscriber exists)
        onlineUsers = await chatClient.onlineUserIds

        // Load avatars in background (non-blocking)
        avatarTask?.cancel()
        avatarTask = Task { await loadAvatars(chatClient: chatClient, currentUserId: currentUserId) }
    }

    func refresh(chatClient: ChatClient) async {
        // Debounce: skip if refreshed < 2s ago
        guard Date().timeIntervalSince(lastRefreshTime) > 2 else {
            #if DEBUG
            print("DEBUG ConversationListVM: refresh debounced")
            #endif
            return
        }
        lastRefreshTime = Date()

        let currentUserId = await chatClient.userId ?? ""
        let convs = await chatClient.listConversations()
        if !convs.isEmpty {
            // Sync unread counts from server (authoritative source)
            for conv in convs {
                unreadCounts[conv.id] = conv.unreadCount
            }
            conversations = sortConversations(convs, currentUserId: currentUserId)
            // Save to cache
            MessageCache.saveConversations(email: email, conversations: convs, cacheKey: localKey)
        }
        invitations = await chatClient.listInvitations()

        // Refresh avatars in background
        avatarTask?.cancel()
        avatarTask = Task { await loadAvatars(chatClient: chatClient, currentUserId: currentUserId) }
    }

    func toggleFavorite(convId: String, email: String) {
        if favorites.contains(convId) {
            favorites.remove(convId)
        } else {
            favorites.insert(convId)
        }
        try? KeyStorage.saveFavorites(email: email, favorites: favorites, localKey: localKey)

        // Re-sort
        let userId = conversations.first?.createdBy ?? ""
        conversations = sortConversations(conversations, currentUserId: userId)
    }

    func forceRefresh(chatClient: ChatClient) async {
        lastRefreshTime = .distantPast
        await refresh(chatClient: chatClient)
    }

    func updateAvatar(convId: String, data: Data) {
        avatarCache[convId] = data
        // Persist to disk so it survives load() re-reads
        MessageCache.saveAvatar(email: email, key: convId, data: data, cacheKey: localKey)
    }

    func markConversationRead(convId: String) {
        unreadCounts[convId] = 0
    }

    func incrementUnread(convId: String) {
        unreadCounts[convId, default: 0] += 1
    }

    private func sortConversations(_ convs: [Conversation], currentUserId: String) -> [Conversation] {
        var result = convs.map { conv -> Conversation in
            var c = conv
            c.isFavorite = favorites.contains(conv.id)
            c.unreadCount = unreadCounts[conv.id] ?? conv.unreadCount
            return c
        }

        result.sort { a, b in
            // Favorites first
            if a.isFavorite != b.isFavorite { return a.isFavorite }
            // Online DMs next
            let aOnline = a.dmPartnerId(currentUserId: currentUserId).map { onlineUsers.contains($0) } ?? false
            let bOnline = b.dmPartnerId(currentUserId: currentUserId).map { onlineUsers.contains($0) } ?? false
            if aOnline != bOnline { return aOnline }
            // Alphabetical
            return a.displayName(currentUserId: currentUserId).lowercased() < b.displayName(currentUserId: currentUserId).lowercased()
        }

        return result
    }

    private func startNotificationListener(chatClient: ChatClient, email: String) {
        notificationTask?.cancel()
        notificationTask = Task {
            for await notification in await chatClient.makeNotificationStream() {
                await handleNotification(notification, chatClient: chatClient, email: email)
            }
        }
    }

    @MainActor
    private func handleNotification(_ notification: ChatNotification, chatClient: ChatClient, email: String) {
        switch notification {
        case .newMessage(let data):
            if let convId = data["conversation_id"] as? String {
                incrementUnread(convId: convId)
            }
        case .onlineUsers(let userIds):
            onlineUsers = Set(userIds)
        case .userOnline(let userId):
            onlineUsers.insert(userId)
        case .userOffline(let userId):
            onlineUsers.remove(userId)
        case .conversationCreated, .memberAdded, .memberRemoved, .conversationRenamed, .conversationDeleted:
            refreshTask?.cancel()
            refreshTask = Task { await refresh(chatClient: chatClient) }
        case .groupInvitation:
            Task { invitations = await chatClient.listInvitations() }
        case .reconnected:
            #if DEBUG
            print("DEBUG ConversationListVM: reconnected — refreshing")
            #endif
            refreshTask?.cancel()
            refreshTask = Task { await refresh(chatClient: chatClient) }
        case .connectionStateChanged(let connected):
            if !connected {
                #if DEBUG
                print("DEBUG ConversationListVM: disconnected")
                #endif
            }
        default:
            break
        }
    }

    private func loadAvatars(chatClient: ChatClient, currentUserId: String) async {
        await withTaskGroup(of: (String, Data?).self) { group in
            for conv in conversations {
                let convId = conv.id
                // Skip if already cached in memory
                if avatarCache[convId] != nil { continue }
                if conv.isGroup {
                    // Only fetch if group has an avatar file
                    if conv.avatarFile != nil {
                        group.addTask {
                            let data = await chatClient.getGroupAvatar(convId: convId)
                            return (convId, data)
                        }
                    }
                } else {
                    // DM: fetch partner's avatar
                    if let partnerId = conv.dmPartnerId(currentUserId: currentUserId) {
                        group.addTask {
                            let data = await chatClient.getAvatar(userId: partnerId)
                            return (convId, data)
                        }
                    }
                }
            }

            let emailCapture = email
            let keyCapture = localKey
            for await (convId, data) in group {
                if let data = data {
                    avatarCache[convId] = data
                    // Save to disk cache
                    MessageCache.saveAvatar(email: emailCapture, key: convId, data: data, cacheKey: keyCapture)
                }
            }
        }
    }

    func stop() {
        notificationTask?.cancel()
        notificationTask = nil
        avatarTask?.cancel()
        avatarTask = nil
        refreshTask?.cancel()
        refreshTask = nil
    }
}
