import Testing
import Foundation
import BitFoundation
@testable import bitchat

struct NotificationStreamAssemblerTests {
    private func makePacket(timestamp: UInt64 = 0x0102030405) -> BitchatPacket {
        let sender = Data([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77])
        return BitchatPacket(
            type: MessageType.message.rawValue,
            senderID: sender,
            recipientID: nil,
            timestamp: timestamp,
            payload: Data([0xDE, 0xAD, 0xBE, 0xEF]),
            signature: nil,
            ttl: 3
        )
    }

    @Test func assemblesSingleFrameAcrossChunks() throws {
        var assembler = NotificationStreamAssembler()
        let packet = makePacket()
        let frame = try #require(packet.toBinaryData(padding: false), "Failed to encode packet")

        #expect(BinaryProtocol.decode(frame) != nil)
        let payloadLen = (Int(frame[12]) << 8) | Int(frame[13])
        #expect(payloadLen == packet.payload.count)

        let splitIndex = min(20, max(1, frame.count / 2))
        let first = frame.prefix(splitIndex)
        let second = frame.suffix(from: splitIndex)
        #expect(first.count + second.count == frame.count)

        var result = assembler.append(first)
        #expect(result.frames.isEmpty)
        #expect(result.droppedPrefixes.isEmpty)
        #expect(!result.reset)

        result = assembler.append(second)
        #expect(result.frames.count == 1)
        #expect(result.droppedPrefixes.isEmpty)
        #expect(!result.reset)

        let frameData = try #require(result.frames.first, "Missing frame data")
        #expect(frameData.count == frame.count)
        
        let decoded = try #require(BinaryProtocol.decode(frameData), "Failed to decode frame")
        #expect(decoded.type == packet.type)
        #expect(decoded.payload == packet.payload)
        #expect(decoded.senderID == packet.senderID)
        #expect(decoded.timestamp == packet.timestamp)

        var directAssembler = NotificationStreamAssembler()
        let directResult = directAssembler.append(frame)
        #expect(directResult.frames.first?.count == frame.count)
    }

    @Test func assemblesMultipleFramesSequentially() throws {
        var assembler = NotificationStreamAssembler()
        let packet1 = makePacket(timestamp: 0xABC)
        let packet2 = makePacket(timestamp: 0xDEF)

        let frame1 = try #require(packet1.toBinaryData(padding: false), "Failed to encode packet")
        let frame2 = try #require(packet2.toBinaryData(padding: false), "Failed to encode packet")

        var combined = Data()
        combined.append(frame1)
        combined.append(frame2)
        let firstChunk = combined.prefix(20)
        let secondChunk = combined.suffix(from: 20)

        var result = assembler.append(firstChunk)
        #expect(result.frames.isEmpty)

        result = assembler.append(secondChunk)
        #expect(result.frames.count == 2)
        
        let decoded1 = try #require(BinaryProtocol.decode(result.frames[0]), "Failed to decode frame")
        let decoded2 = try #require(BinaryProtocol.decode(result.frames[1]), "Failed to decode frame")
        #expect(decoded1.timestamp == packet1.timestamp)
        #expect(decoded2.timestamp == packet2.timestamp)
    }

    @Test func dropsInvalidPrefixByte() throws {
        var assembler = NotificationStreamAssembler()
        let packet = makePacket(timestamp: 0xF00)
        let frame = try #require(packet.toBinaryData(padding: false), "Failed to encode packet")
        var noisyFrame = Data([0x00])
        noisyFrame.append(frame)

        let result = assembler.append(noisyFrame)
        #expect(result.droppedPrefixes == [0x00])
        #expect(result.frames.count == 1)
        #expect(result.reset == false)

        let decoded = try #require(BinaryProtocol.decode(result.frames[0]), "Failed to decode frame after drop")
        #expect(decoded.timestamp == packet.timestamp)
    }

    @Test func discardsPKCSPaddingBetweenNotificationFrames() throws {
        var assembler = NotificationStreamAssembler()
        let packet1 = makePacket(timestamp: 0x111)
        let packet2 = makePacket(timestamp: 0x222)
        let paddedFrame1 = try #require(packet1.toBinaryData(padding: true), "Failed to encode first padded packet")
        let paddedFrame2 = try #require(packet2.toBinaryData(padding: true), "Failed to encode second padded packet")

        var result = assembler.append(paddedFrame1)
        #expect(result.frames.count == 1)
        #expect(result.droppedPrefixes.isEmpty)
        #expect(result.reset == false)

        result = assembler.append(paddedFrame2)
        #expect(result.frames.count == 1)
        #expect(result.droppedPrefixes.isEmpty)
        #expect(result.reset == false)

        let decoded = try #require(BinaryProtocol.decode(result.frames[0]), "Failed to decode second frame")
        #expect(decoded.timestamp == packet2.timestamp)
    }

    @Test
    func testAssemblesCompressedLargeFrame() throws {
        var assembler = NotificationStreamAssembler()

        // Keep the fixture below FileTransferLimits.maxPayloadBytes so encoding succeeds while still exercising compression.
        let largeContent = Data(repeating: 0x41, count: 600_000)
        let filePacket = BitchatFilePacket(
            fileName: "large.bin",
            fileSize: UInt64(largeContent.count),
            mimeType: "application/octet-stream",
            content: largeContent
        )
        let tlvPayload = try #require(filePacket.encode(), "Failed to encode file packet")

        let senderID = Data(repeating: 0xAA, count: BinaryProtocol.senderIDSize)
        let packet = BitchatPacket(
            type: MessageType.fileTransfer.rawValue,
            senderID: senderID,
            recipientID: nil,
            timestamp: 0x010203040506,
            payload: tlvPayload,
            signature: nil,
            ttl: 3,
            version: 2
        )

        let frame = try #require(packet.toBinaryData(padding: false), "Failed to encode packet frame")

        #expect(BinaryProtocol.Offsets.flags < frame.count)
        let flags = frame[frame.startIndex + BinaryProtocol.Offsets.flags]
        #expect((flags & BinaryProtocol.Flags.isCompressed) != 0, "Frame should be compressed for large payloads")

        let splitIndex = min(4096, frame.count / 2)
        var result = assembler.append(frame.prefix(splitIndex))
        #expect(result.frames.isEmpty)

        result = assembler.append(frame.suffix(from: splitIndex))
        #expect(result.frames.count == 1)
        #expect(result.droppedPrefixes.isEmpty)
        #expect(result.reset == false)

        let assembled = try #require(result.frames.first, "Missing assembled frame")
        #expect(assembled.count == frame.count)

        let decodedPacket = try #require(BinaryProtocol.decode(assembled), "Failed to decode compressed frame")
        #expect(decodedPacket.payload.count == tlvPayload.count)

        let decodedFile = try #require(BitchatFilePacket.decode(decodedPacket.payload), "Failed to decode TLV payload")
        #expect(decodedFile.fileName == filePacket.fileName)
        #expect(decodedFile.mimeType == filePacket.mimeType)
        #expect(decodedFile.content.count == largeContent.count)
        #expect(decodedFile.content.prefix(32) == largeContent.prefix(32))
    }
}
