import Foundation

/// Adaptive chunking logic for sync payloads
enum ChunkingStrategy {
    /// Maximum payload size target in bytes (100MB — server allows 500MB, nginx unlimited)
    static let maxChunkSizeBytes = 100_000_000

    /// Base overhead for conversation wrapper JSON structure
    static let baseOverhead = 200

    /// Estimate the JSON payload size for a single message
    static func estimateMessageSize(_ msg: iMessage) -> Int {
        var size = 500 // Base overhead for JSON structure and fixed fields

        if let text = msg.text { size += text.utf8.count }
        if let attributedBody = msg.attributedBody { size += attributedBody.utf8.count }

        for attachment in msg.attachments {
            size += 100
            if let data = attachment.data { size += data.utf8.count }
            if let filename = attachment.filename { size += filename.utf8.count }
            if let mimeType = attachment.mimeType { size += mimeType.utf8.count }
            if let transferName = attachment.transferName { size += transferName.utf8.count }
        }

        if let styleId = msg.expressiveSendStyleId { size += styleId.utf8.count }
        if let guid = msg.associatedMessageGuid { size += guid.utf8.count }
        if let replyGuid = msg.replyToGuid { size += replyGuid.utf8.count }
        if let originGuid = msg.threadOriginatorGuid { size += originGuid.utf8.count }
        if let groupTitle = msg.groupTitle { size += groupTitle.utf8.count }
        if let bundleId = msg.balloonBundleId { size += bundleId.utf8.count }
        if let senderName = msg.senderDisplayName { size += senderName.utf8.count }

        return size
    }

    /// Maximum number of messages per batch request during initial sync.
    /// Configurable via config.json key `imessageMaxMessagesPerBatch`.
    static var maxMessagesPerBatch: Int {
        let v = UserDefaults.standard.integer(forKey: "imessageMaxMessagesPerBatch")
        return v > 0 ? v : 300
    }

    /// Maximum number of conversations per batch request during initial sync.
    /// Configurable via config.json key `imessageMaxConversationsPerBatch`.
    static var maxConversationsPerBatch: Int {
        let v = UserDefaults.standard.integer(forKey: "imessageMaxConversationsPerBatch")
        return v > 0 ? v : 5
    }

    /// Chunk a full list of conversation payloads into batches that stay
    /// under `maxMessagesPerBatch` and `maxConversationsPerBatch`.
    /// Each returned group is suitable for one `syncMessagesBatch` call.
    static func chunkConversationPayloads<T>(
        _ payloads: [T],
        messageCount: (T) -> Int
    ) -> [[T]] {
        guard !payloads.isEmpty else { return [] }

        var chunks: [[T]] = []
        var current: [T] = []
        var currentMessages = 0

        for p in payloads {
            let msgs = messageCount(p)
            let wouldExceed = currentMessages + msgs > maxMessagesPerBatch
                || current.count + 1 > maxConversationsPerBatch
            if wouldExceed && !current.isEmpty {
                chunks.append(current)
                current = []
                currentMessages = 0
            }
            current.append(p)
            currentMessages += msgs
        }
        if !current.isEmpty {
            chunks.append(current)
        }
        return chunks
    }

    /// Create adaptive chunks based on payload size estimation
    static func createAdaptiveChunks(_ messages: [iMessage]) -> [[iMessage]] {
        guard !messages.isEmpty else { return [] }

        var chunks: [[iMessage]] = []
        var currentChunk: [iMessage] = []
        var currentSize = baseOverhead

        for msg in messages {
            let msgSize = estimateMessageSize(msg)

            if msgSize > maxChunkSizeBytes {
                if !currentChunk.isEmpty {
                    chunks.append(currentChunk)
                    currentChunk = []
                    currentSize = baseOverhead
                }
                chunks.append([msg])
                continue
            }

            if currentSize + msgSize > maxChunkSizeBytes && !currentChunk.isEmpty {
                chunks.append(currentChunk)
                currentChunk = []
                currentSize = baseOverhead
            }

            currentChunk.append(msg)
            currentSize += msgSize
        }

        if !currentChunk.isEmpty {
            chunks.append(currentChunk)
        }

        return chunks
    }
}
