import SwiftUI

// MARK: - MessageAvatarView

/// Displays an avatar attachment inside a message bubble.
///
/// Android parity: `AvatarAttachment` composable — download only on user tap,
/// NOT auto-download. After successful download, saves to AvatarRepository.
///
/// States:
/// 1. **Cached** — avatar already in AttachmentCache, display immediately
/// 2. **Not downloaded** — show placeholder + "Tap to download"
/// 3. **Downloading** — show placeholder + spinner
/// 4. **Downloaded** — display avatar, saved to AvatarRepository
/// 5. **Error** — "Tap to retry"
struct MessageAvatarView: View {

    let attachment: MessageAttachment
    let message: ChatMessage
    let outgoing: Bool

    @State private var avatarImage: UIImage?
    @State private var blurImage: UIImage?
    @State private var isDownloading = false
    @State private var downloadError = false
    @State private var showAvatar = false

    /// Avatar circle diameter (desktop parity: 60px).
    private let avatarSize: CGFloat = 56

    /// Whether the avatar needs to be downloaded from CDN.
    private var needsDownload: Bool {
        avatarImage == nil && !isDownloading && !downloadError
    }

    var body: some View {
        HStack(spacing: 10) {
            // Avatar circle (left side)
            avatarCircle

            // Metadata (right side)
            VStack(alignment: .leading, spacing: 3) {
                // Title row: "Avatar" + lock icon
                HStack(spacing: 4) {
                    Text("Avatar")
                        .font(.system(size: 14, weight: .medium))
                        .foregroundStyle(outgoing ? .white : RosettaColors.Adaptive.text)

                    Image(systemName: "lock.fill")
                        .font(.system(size: 10))
                        .foregroundStyle(Color.green.opacity(0.8))
                }

                // Description / status
                if isDownloading {
                    HStack(spacing: 4) {
                        ProgressView()
                            .tint(outgoing ? .white.opacity(0.6) : Color(hex: 0x008BFF))
                            .scaleEffect(0.7)
                        Text("Downloading...")
                            .font(.system(size: 11))
                            .foregroundStyle(outgoing ? .white.opacity(0.4) : RosettaColors.Adaptive.textSecondary)
                    }
                } else if downloadError {
                    Text("Tap to retry")
                        .font(.system(size: 12))
                        .foregroundStyle(RosettaColors.error)
                } else if avatarImage != nil {
                    Text(DatabaseManager.isGroupDialogKey(message.toPublicKey)
                        ? "Shared group photo."
                        : "Shared profile photo.")
                        .font(.system(size: 12))
                        .foregroundStyle(outgoing ? .white.opacity(0.5) : RosettaColors.Adaptive.textSecondary)
                        .lineLimit(2)
                } else {
                    Text("Tap to download")
                        .font(.system(size: 12))
                        .foregroundStyle(outgoing ? .white.opacity(0.5) : RosettaColors.Adaptive.textSecondary)
                }
            }

            Spacer(minLength: 0)
        }
        .padding(.horizontal, 10)
        .padding(.vertical, 8)
        .contentShape(Rectangle())
        // BubbleContextMenuOverlay blocks all SwiftUI onTapGesture —
        // download triggered via notification from overlay's onTap callback.
        .onReceive(NotificationCenter.default.publisher(for: .triggerAttachmentDownload)) { notif in
            if let id = notif.object as? String, id == attachment.id {
                if needsDownload || downloadError {
                    downloadAvatar()
                }
            }
        }
        .task {
            await loadFromCache()
            if avatarImage == nil {
                await decodeBlurHash()
            }
        }
    }

    // MARK: - Avatar Circle

    @ViewBuilder
    private var avatarCircle: some View {
        ZStack {
            if let avatarImage {
                Image(uiImage: avatarImage)
                    .resizable()
                    .scaledToFill()
                    .frame(width: avatarSize, height: avatarSize)
                    .clipShape(Circle())
                    .scaleEffect(showAvatar ? 1.0 : 0.5)
                    .animation(.spring(response: 0.4, dampingFraction: 0.5), value: showAvatar)
                    .onAppear { showAvatar = true }
            } else if let blurImage {
                // Android parity: blurhash preview in circle before download
                Image(uiImage: blurImage)
                    .resizable()
                    .scaledToFill()
                    .frame(width: avatarSize, height: avatarSize)
                    .clipShape(Circle())
                    .overlay {
                        if isDownloading {
                            Circle()
                                .fill(Color.black.opacity(0.4))
                            ProgressView()
                                .tint(.white)
                                .scaleEffect(0.8)
                        } else if downloadError {
                            Circle()
                                .fill(Color.black.opacity(0.4))
                            Image(systemName: "arrow.clockwise")
                                .font(.system(size: 18))
                                .foregroundStyle(.white.opacity(0.8))
                        } else {
                            Circle()
                                .fill(Color.black.opacity(0.4))
                            Image(systemName: "arrow.down")
                                .font(.system(size: 18, weight: .medium))
                                .foregroundStyle(.white)
                        }
                    }
            } else {
                Circle()
                    .fill(outgoing ? Color.white.opacity(0.15) : Color.white.opacity(0.08))
                    .frame(width: avatarSize, height: avatarSize)
                    .overlay {
                        if isDownloading {
                            ProgressView()
                                .tint(.white.opacity(0.5))
                        } else if downloadError {
                            Image(systemName: "arrow.clockwise")
                                .font(.system(size: 18))
                                .foregroundStyle(.white.opacity(0.4))
                        } else {
                            Image(systemName: "person.fill")
                                .font(.system(size: 24))
                                .foregroundStyle(.white.opacity(0.3))
                        }
                    }
            }
        }
    }

    // MARK: - BlurHash Decoding

    /// Android parity: decode blurhash from preview as 32×32 placeholder.
    /// Shared static cache with half-eviction (same pattern as MessageImageView).
    @MainActor private static var blurHashCache: [String: UIImage] = [:]

    private func decodeBlurHash() async {
        let hash = extractBlurHash(from: attachment.preview)
        guard !hash.isEmpty else { return }
        // Fast path: cache hit (synchronous)
        if let cached = Self.blurHashCache[hash] {
            blurImage = cached
            return
        }
        // Slow path: DCT decode off main thread
        let result = await Task.detached(priority: .userInitiated) {
            UIImage.fromBlurHash(hash, width: 32, height: 32)
        }.value
        guard !Task.isCancelled, let result else { return }
        if Self.blurHashCache.count > 200 {
            let keysToRemove = Array(Self.blurHashCache.keys.prefix(100))
            for key in keysToRemove { Self.blurHashCache.removeValue(forKey: key) }
        }
        Self.blurHashCache[hash] = result
        blurImage = result
    }

    /// Extracts the blurhash from preview string.
    /// Format: "tag::blurhash" → returns "blurhash".
    private func extractBlurHash(from preview: String) -> String {
        AttachmentPreviewCodec.blurHash(from: preview)
    }

    // MARK: - Download

    private func loadFromCache() async {
        // Fast path: memory-only NSCache hit (no disk/crypto on main thread).
        if let cached = AttachmentCache.shared.cachedImage(forAttachmentId: attachment.id) {
            avatarImage = cached
            showAvatar = true // No animation for cached — show immediately
            return
        }
        // Slow path: disk I/O + crypto — run off main thread with semaphore
        // Android parity: ImageLoadSemaphore limits to 3 concurrent decode ops
        let attachmentId = attachment.id
        await ImageLoadLimiter.shared.acquire()
        let loaded = await Task.detached(priority: .userInitiated) {
            AttachmentCache.shared.loadImage(forAttachmentId: attachmentId)
        }.value
        await ImageLoadLimiter.shared.release()
        if !Task.isCancelled, let loaded {
            avatarImage = loaded
            showAvatar = true
            return
        }
        // Outgoing avatar: sender is me — load from AvatarRepository (always available locally)
        if outgoing {
            let myKey = SessionManager.shared.currentPublicKey
            if let myAvatar = AvatarRepository.shared.loadAvatar(publicKey: myKey) {
                avatarImage = myAvatar
                showAvatar = true
                // Backfill AttachmentCache so next render hits fast path
                AttachmentCache.shared.saveImage(myAvatar, forAttachmentId: attachment.id)
            }
        }
    }

    private func downloadAvatar() {
        guard !isDownloading else { return }

        let tag = attachment.effectiveDownloadTag
        guard !tag.isEmpty else {
            downloadError = true
            return
        }

        guard let storedPassword = message.attachmentPassword, !storedPassword.isEmpty else {
            downloadError = true
            return
        }

        isDownloading = true
        downloadError = false

        let server = attachment.transportServer
        Task {
            do {
                let encryptedData = try await TransportManager.shared.downloadFile(tag: tag, server: server)
                let encryptedString = String(decoding: encryptedData, as: UTF8.self)

                let passwords = MessageCrypto.attachmentPasswordCandidates(from: storedPassword)
                let downloadedImage = decryptAndParseImage(
                    encryptedString: encryptedString, passwords: passwords
                )

                await MainActor.run {
                    if let downloadedImage {
                        avatarImage = downloadedImage
                        AttachmentCache.shared.saveImage(downloadedImage, forAttachmentId: attachment.id)
                        // Desktop parity: in groups, avatar saves as group avatar.
                        // In personal chats, saves as sender's avatar.
                        let avatarKey = DatabaseManager.isGroupDialogKey(message.toPublicKey)
                            ? message.toPublicKey
                            : message.fromPublicKey
                        if let jpegData = downloadedImage.jpegData(compressionQuality: 0.85) {
                            let base64 = jpegData.base64EncodedString()
                            AvatarRepository.shared.saveAvatarFromBase64(base64, publicKey: avatarKey)
                        }
                    } else {
                        downloadError = true
                    }
                    isDownloading = false
                }
            } catch {
                await MainActor.run {
                    downloadError = true
                    isDownloading = false
                }
            }
        }
    }

    /// Tries each password candidate and validates the decrypted content is a real image.
    private func decryptAndParseImage(encryptedString: String, passwords: [String]) -> UIImage? {
        let crypto = CryptoManager.shared
        for password in passwords {
            do {
                let data = try crypto.decryptWithPassword(
                    encryptedString, password: password, requireCompression: true
                )
                if let img = parseImageData(data) { return img }
            } catch {
                continue
            }
        }
        // Fallback: try without requireCompression (legacy uncompressed payloads)
        for password in passwords {
            do {
                let data = try crypto.decryptWithPassword(
                    encryptedString, password: password
                )
                if let img = parseImageData(data) { return img }
            } catch {
                continue
            }
        }
        return nil
    }

    /// Android parity: `base64ToBitmap()` with subsampling to max 4096px.
    private func parseImageData(_ data: Data) -> UIImage? {
        if let str = String(data: data, encoding: .utf8) {
            if str.hasPrefix("data:"),
               let commaIndex = str.firstIndex(of: ",") {
                let base64Part = String(str[str.index(after: commaIndex)...])
                if let imageData = Data(base64Encoded: base64Part),
                   let img = AttachmentCache.downsampledImage(from: imageData) {
                    return img
                }
            } else if let imageData = Data(base64Encoded: str),
                      let img = AttachmentCache.downsampledImage(from: imageData) {
                return img
            }
        }
        return AttachmentCache.downsampledImage(from: data)
    }

    /// Extracts the server tag from preview string.
    /// Format: "tag::blurhash" → returns "tag".
    private func extractTag(from preview: String) -> String {
        AttachmentPreviewCodec.downloadTag(from: preview)
    }
}
