import Foundation
import Testing
@testable import IMessageSync

private func makeTestMessage(
    guid: String = "test-guid",
    text: String? = "Hello",
    attachments: [iMessageAttachment] = [],
    expressiveSendStyleId: String? = nil,
    associatedMessageGuid: String? = nil,
    senderDisplayName: String? = nil
) -> iMessage {
    iMessage(
        guid: guid,
        conversationId: "conv-001",
        senderId: nil,
        isFromMe: true,
        date: Date(),
        dateDelivered: nil,
        dateRead: nil,
        text: text,
        attributedBody: nil,
        associatedMessageType: nil,
        associatedMessageGuid: associatedMessageGuid,
        isAudioMessage: false,
        expressiveSendStyleId: expressiveSendStyleId,
        replyToGuid: nil,
        threadOriginatorGuid: nil,
        groupTitle: nil,
        balloonBundleId: nil,
        service: "iMessage",
        senderIdentifier: nil,
        senderDisplayName: senderDisplayName,
        senderPhoneNumber: nil,
        senderEmail: nil,
        attachments: attachments,
        attachmentsCount: attachments.count,
        attachmentsTotalSize: attachments.reduce(0) { $0 + $1.size },
        attachmentsFiletypes: Array(Set(attachments.compactMap { $0.mimeType }))
    )
}

@Suite("Adaptive Chunking")
struct AdaptiveChunkingTests {

    @Test func emptyMessagesProducesEmptyChunks() {
        let chunks = ChunkingStrategy.createAdaptiveChunks([])
        #expect(chunks.isEmpty)
    }

    @Test func singleSmallMessageProducesOneChunk() {
        let chunks = ChunkingStrategy.createAdaptiveChunks([makeTestMessage()])
        #expect(chunks.count == 1)
        #expect(chunks[0].count == 1)
    }

    @Test func multipleSmallMessagesUnderLimitProduceSingleChunk() {
        let messages = (0..<10).map { makeTestMessage(guid: "msg-\($0)") }
        let chunks = ChunkingStrategy.createAdaptiveChunks(messages)
        #expect(chunks.count == 1)
        #expect(chunks[0].count == 10)
    }

    @Test func messagesExceedingLimitSplitIntoMultipleChunks() {
        let largeData = String(repeating: "A", count: 60_000_000)
        let attachment = iMessageAttachment(
            filename: "large.bin",
            mimeType: "application/octet-stream",
            transferName: "large.bin",
            size: 60_000_000,
            data: largeData
        )
        let messages = (0..<3).map {
            makeTestMessage(guid: "large-\($0)", attachments: [attachment])
        }
        let chunks = ChunkingStrategy.createAdaptiveChunks(messages)
        #expect(chunks.count >= 2)
    }

    @Test func singleOversizedMessageGetsOwnChunk() {
        let hugeData = String(repeating: "X", count: 110_000_000)
        let attachment = iMessageAttachment(
            filename: "huge.bin",
            mimeType: "application/octet-stream",
            transferName: "huge.bin",
            size: 110_000_000,
            data: hugeData
        )
        let messages = [
            makeTestMessage(guid: "small-1"),
            makeTestMessage(guid: "huge", attachments: [attachment]),
            makeTestMessage(guid: "small-2"),
        ]
        let chunks = ChunkingStrategy.createAdaptiveChunks(messages)
        #expect(chunks.count == 3)
        #expect(chunks[0].count == 1)
        #expect(chunks[0][0].guid == "small-1")
        #expect(chunks[1].count == 1)
        #expect(chunks[1][0].guid == "huge")
        #expect(chunks[2].count == 1)
        #expect(chunks[2][0].guid == "small-2")
    }

    @Test func baseOverheadIsAccounted() {
        #expect(ChunkingStrategy.baseOverhead == 200)
    }

    @Test func maxChunkSizeBytesIs100MB() {
        #expect(ChunkingStrategy.maxChunkSizeBytes == 100_000_000)
    }
}

@Suite("Message Size Estimation")
struct MessageSizeEstimationTests {

    @Test func baseOverheadIs500Bytes() {
        let size = ChunkingStrategy.estimateMessageSize(makeTestMessage(text: nil))
        #expect(size == 500)
    }

    @Test func textFieldAddsUtf8ByteCount() {
        let text = "Hello, World!"
        let size = ChunkingStrategy.estimateMessageSize(makeTestMessage(text: text))
        #expect(size == 500 + text.utf8.count)
    }

    @Test func attachmentDataAddsUtf8ByteCount() {
        let data = "base64encodedcontent"
        let attachment = iMessageAttachment(
            filename: "test.jpg",
            mimeType: "image/jpeg",
            transferName: "test.jpg",
            size: 1000,
            data: data
        )
        let size = ChunkingStrategy.estimateMessageSize(makeTestMessage(text: nil, attachments: [attachment]))
        let expected = 500 + 100 + data.utf8.count + "test.jpg".utf8.count + "image/jpeg".utf8.count + "test.jpg".utf8.count
        #expect(size == expected)
    }

    @Test func multipleAttachmentsSumCorrectly() {
        let att1 = iMessageAttachment(filename: "a.jpg", mimeType: "image/jpeg", transferName: nil, size: 100, data: "AAA")
        let att2 = iMessageAttachment(filename: "b.png", mimeType: "image/png", transferName: nil, size: 200, data: "BBB")
        let size = ChunkingStrategy.estimateMessageSize(makeTestMessage(text: nil, attachments: [att1, att2]))
        let expected = 500
            + 100 + "AAA".utf8.count + "a.jpg".utf8.count + "image/jpeg".utf8.count
            + 100 + "BBB".utf8.count + "b.png".utf8.count + "image/png".utf8.count
        #expect(size == expected)
    }

    @Test func nilOptionalFieldsAddZero() {
        let size = ChunkingStrategy.estimateMessageSize(
            makeTestMessage(text: nil, expressiveSendStyleId: nil, associatedMessageGuid: nil, senderDisplayName: nil)
        )
        #expect(size == 500)
    }

    @Test func optionalStringFieldsAddToSize() {
        let size = ChunkingStrategy.estimateMessageSize(
            makeTestMessage(
                text: nil,
                expressiveSendStyleId: "com.apple.MobileSMS.expressivesend.impact",
                senderDisplayName: "Alice"
            )
        )
        let expected = 500
            + "com.apple.MobileSMS.expressivesend.impact".utf8.count
            + "Alice".utf8.count
        #expect(size == expected)
    }
}
