import Combine
import Foundation

// MARK: - GroupMember

struct GroupMember: Identifiable {
    let id: String  // publicKey
    var title: String
    var username: String
    var isAdmin: Bool
    var isOnline: Bool
    var verified: Int
}

// MARK: - GroupInfoViewModel

@MainActor
final class GroupInfoViewModel: ObservableObject {
    let groupDialogKey: String

    @Published var groupTitle: String = ""
    @Published var groupDescription: String = ""
    @Published var members: [GroupMember] = []
    @Published var memberCount: Int = 0
    @Published var isAdmin: Bool = false
    @Published var isLoading: Bool = false
    @Published var isLeaving: Bool = false
    @Published var errorMessage: String?
    @Published var didLeaveGroup: Bool = false
    @Published var inviteString: String?
    @Published var mediaItems: [SharedMediaItem] = []
    @Published var fileItems: [SharedFileItem] = []
    @Published var linkItems: [SharedLinkItem] = []

    private let groupService = GroupService.shared
    private let groupRepo = GroupRepository.shared

    init(groupDialogKey: String) {
        self.groupDialogKey = groupDialogKey
        loadLocalMetadata()
        loadCachedMembers()
    }

    private func loadLocalMetadata() {
        let account = SessionManager.shared.currentPublicKey
        if let meta = groupRepo.groupMetadata(account: account, groupDialogKey: groupDialogKey) {
            groupTitle = meta.title
            groupDescription = meta.description
        }
    }

    /// Instantly populates members from in-memory cache (no network).
    private func loadCachedMembers() {
        let account = SessionManager.shared.currentPublicKey
        guard let cached = groupRepo.cachedMembers(account: account, groupDialogKey: groupDialogKey) else { return }
        let resolved = resolveMembers(keys: cached.memberKeys)
        members = resolved
        memberCount = resolved.count
        isAdmin = cached.adminKey == SessionManager.shared.currentPublicKey
    }

    func loadMembers() async {
        let hasCachedData = !members.isEmpty
        if !hasCachedData { isLoading = true }
        do {
            let memberKeys = try await groupService.requestMembers(groupDialogKey: groupDialogKey)
            let myKey = SessionManager.shared.currentPublicKey

            // Update cache
            groupRepo.updateMemberCache(
                account: myKey,
                groupDialogKey: groupDialogKey,
                memberKeys: memberKeys
            )

            let resolved = resolveMembers(keys: memberKeys)
            let newAdmin = memberKeys.first == myKey
            let newCount = resolved.count

            // Only update @Published properties if data actually changed
            // to avoid unnecessary re-renders / "flash" effect.
            if members.map(\.id) != resolved.map(\.id) {
                members = resolved
            }
            if memberCount != newCount {
                memberCount = newCount
            }
            if isAdmin != newAdmin {
                isAdmin = newAdmin
            }
        } catch {
            if !hasCachedData {
                errorMessage = error.localizedDescription
            }
        }
        if isLoading { isLoading = false }
    }

    /// Resolves public keys into GroupMember objects using DialogRepository.
    private func resolveMembers(keys: [String]) -> [GroupMember] {
        let myKey = SessionManager.shared.currentPublicKey
        var resolved: [GroupMember] = []
        for (index, key) in keys.enumerated() {
            if key == myKey {
                let sm = SessionManager.shared
                let myTitle = sm.displayName.isEmpty ? String(key.prefix(12)) : sm.displayName
                let myUsername = sm.username
                resolved.append(GroupMember(
                    id: key,
                    title: myTitle,
                    username: myUsername,
                    isAdmin: index == 0,
                    isOnline: true,
                    verified: 0
                ))
            } else {
                let dialog = DialogRepository.shared.dialogs[key]
                resolved.append(GroupMember(
                    id: key,
                    title: dialog?.opponentTitle ?? String(key.prefix(12)),
                    username: dialog?.opponentUsername ?? "",
                    isAdmin: index == 0,
                    isOnline: dialog?.isOnline ?? false,
                    verified: dialog?.verified ?? 0
                ))
            }
        }
        return resolved
    }

    func leaveGroup() async {
        isLeaving = true
        do {
            try await groupService.leaveGroup(groupDialogKey: groupDialogKey)
            didLeaveGroup = true
        } catch {
            errorMessage = error.localizedDescription
        }
        isLeaving = false
    }

    func kickMember(publicKey: String) async {
        do {
            try await groupService.kickMember(groupDialogKey: groupDialogKey, memberPublicKey: publicKey)
            members.removeAll { $0.id == publicKey }
            memberCount = members.count
        } catch {
            errorMessage = error.localizedDescription
        }
    }

    func generateInvite() {
        inviteString = groupService.generateInviteString(groupDialogKey: groupDialogKey)
    }

    func loadSharedContent() {
        let messages = MessageRepository.shared.messages(for: groupDialogKey)
        var media: [SharedMediaItem] = []
        var files: [SharedFileItem] = []
        var links: [SharedLinkItem] = []

        // URL detection
        let urlDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)

        for message in messages {
            let senderName = resolveSenderName(for: message.fromPublicKey)

            // Media & Files from attachments
            for attachment in message.attachments {
                switch attachment.type {
                case .image:
                    media.append(SharedMediaItem(
                        attachmentId: attachment.id,
                        messageId: message.id,
                        senderName: senderName,
                        timestamp: message.timestamp,
                        caption: message.text,
                        blurhash: AttachmentPreviewCodec.blurHash(from: attachment.preview)
                    ))
                case .file:
                    let parsed = AttachmentPreviewCodec.parseFilePreview(attachment.preview)
                    files.append(SharedFileItem(
                        attachmentId: attachment.id,
                        messageId: message.id,
                        fileName: parsed.fileName,
                        fileSize: parsed.fileSize,
                        preview: attachment.preview,
                        timestamp: message.timestamp,
                        senderName: senderName
                    ))
                default:
                    break
                }
            }

            // Links from message text
            let displayText = message.text
            if !displayText.isEmpty, let detector = urlDetector {
                let range = NSRange(displayText.startIndex..., in: displayText)
                let matches = detector.matches(in: displayText, range: range)
                for match in matches {
                    if let urlRange = Range(match.range, in: displayText) {
                        let urlString = String(displayText[urlRange])
                        links.append(SharedLinkItem(
                            url: urlString,
                            messageId: message.id,
                            context: String(displayText.prefix(200)),
                            timestamp: message.timestamp,
                            senderName: senderName
                        ))
                    }
                }
            }
        }

        mediaItems = Array(media.suffix(30).reversed())
        fileItems = Array(files.suffix(30).reversed())
        linkItems = Array(links.suffix(30).reversed())
    }

    private func resolveSenderName(for publicKey: String) -> String {
        if let dialog = DialogRepository.shared.dialogs[publicKey] {
            return dialog.opponentTitle.isEmpty ? String(publicKey.prefix(8)) : dialog.opponentTitle
        } else if publicKey == SessionManager.shared.currentPublicKey {
            return SessionManager.shared.displayName
        }
        return String(publicKey.prefix(8))
    }
}
