import Foundation

/// Message packet (0x06) — sending and receiving encrypted messages.
struct PacketMessage: Packet {
    static let packetId = 0x06

    var fromPublicKey: String = ""
    var toPublicKey: String = ""
    var content: String = ""           // XChaCha20-Poly1305 encrypted (hex)
    var chachaKey: String = ""         // ECDH-encrypted key+nonce
    var timestamp: Int64 = 0
    var privateKey: String = ""        // Hash for server auth
    var messageId: String = ""
    var attachments: [MessageAttachment] = []
    var aesChachaKey: String = ""      // ChaCha key+nonce encrypted by sender
    /// True when payload could not be parsed in any known compatibility layout.
    var isMalformed: Bool = false
    /// Compact parser fingerprint for diagnostics (no sensitive payload data).
    var malformedFingerprint: String = ""

    private struct ParsedPacketMessage {
        let fromPublicKey: String
        let toPublicKey: String
        let content: String
        let chachaKey: String
        let timestamp: Int64
        let privateKey: String
        let messageId: String
        let attachments: [MessageAttachment]
        let aesChachaKey: String
    }

    func write(to stream: Stream) {
        // Match Android field order exactly
        stream.writeString(fromPublicKey)
        stream.writeString(toPublicKey)
        stream.writeString(content)
        stream.writeString(chachaKey)
        stream.writeInt64(timestamp)
        stream.writeString(privateKey)
        stream.writeString(messageId)
        stream.writeInt8(attachments.count)

        for attachment in attachments {
            stream.writeString(attachment.id)
            stream.writeString(attachment.preview)
            stream.writeString(attachment.blob)
            stream.writeInt8(attachment.type.rawValue)
            stream.writeString(attachment.transportTag)
            stream.writeString(attachment.transportServer)
        }
        stream.writeString(aesChachaKey)
    }

    mutating func read(from stream: Stream) {
        let startPointer = stream.getReadPointerBits()
        var parseErrors: [String] = []

        for attachmentMetaFieldCount in [4, 2, 0] {
            stream.setReadPointerBits(startPointer)
            do {
                let parsed = try Self.parseFromStream(
                    stream,
                    attachmentMetaFieldCount: attachmentMetaFieldCount
                )
                if stream.hasRemainingBits() {
                    parseErrors.append(
                        "meta\(attachmentMetaFieldCount):trailing_bits=\(stream.remainingBits())"
                    )
                    continue
                }

                fromPublicKey = parsed.fromPublicKey
                toPublicKey = parsed.toPublicKey
                content = parsed.content
                chachaKey = parsed.chachaKey
                timestamp = parsed.timestamp
                privateKey = parsed.privateKey
                messageId = parsed.messageId
                attachments = parsed.attachments
                aesChachaKey = parsed.aesChachaKey
                isMalformed = false
                malformedFingerprint = ""
                return
            } catch {
                parseErrors.append("meta\(attachmentMetaFieldCount):\(Self.errorFingerprint(error))")
            }
        }

        // Hard-fail parse: preserve zero/default fields and mark packet malformed.
        fromPublicKey = ""
        toPublicKey = ""
        content = ""
        chachaKey = ""
        timestamp = 0
        privateKey = ""
        messageId = ""
        attachments = []
        aesChachaKey = ""
        isMalformed = true
        malformedFingerprint = parseErrors.isEmpty
            ? "packet06_parse_failed"
            : parseErrors.joined(separator: "|")
    }

    private static func parseFromStream(
        _ parser: Stream,
        attachmentMetaFieldCount: Int
    ) throws -> ParsedPacketMessage {
        let parsedFromPublicKey = try parser.readStringStrict()
        let parsedToPublicKey = try parser.readStringStrict()
        let parsedContent = try parser.readStringStrict()
        let parsedChachaKey = try parser.readStringStrict()
        let parsedTimestamp = try parser.readInt64Strict()
        let parsedPrivateKey = try parser.readStringStrict()
        let parsedMessageId = try parser.readStringStrict()

        let attachmentCount = max(try parser.readInt8Strict(), 0)
        var parsedAttachments: [MessageAttachment] = []
        parsedAttachments.reserveCapacity(attachmentCount)

        for _ in 0..<attachmentCount {
            let id = try parser.readStringStrict()
            let preview = try parser.readStringStrict()
            let blob = try parser.readStringStrict()
            let type = AttachmentType(rawValue: try parser.readInt8Strict()) ?? .image

            let transportTag: String
            let transportServer: String
            if attachmentMetaFieldCount >= 2 {
                transportTag = try parser.readStringStrict()
                transportServer = try parser.readStringStrict()
            } else {
                transportTag = ""
                transportServer = ""
            }

            // Older Android builds may contain extra metadata fields.
            if attachmentMetaFieldCount >= 4 {
                _ = try parser.readStringStrict() // encodedFor
                _ = try parser.readStringStrict() // encoder
            }

            parsedAttachments.append(MessageAttachment(
                id: id,
                preview: preview,
                blob: blob,
                type: type,
                transportTag: transportTag,
                transportServer: transportServer
            ))
        }

        let parsedAesChachaKey = try parser.readStringStrict()
        return ParsedPacketMessage(
            fromPublicKey: parsedFromPublicKey,
            toPublicKey: parsedToPublicKey,
            content: parsedContent,
            chachaKey: parsedChachaKey,
            timestamp: parsedTimestamp,
            privateKey: parsedPrivateKey,
            messageId: parsedMessageId,
            attachments: parsedAttachments,
            aesChachaKey: parsedAesChachaKey
        )
    }

    private static func errorFingerprint(_ error: Error) -> String {
        switch error {
        case PacketBitStreamError.underflow(let operation, let neededBits, let remainingBits):
            return "underflow:\(operation):\(neededBits):\(remainingBits)"
        case PacketBitStreamError.invalidStringLength(let length):
            return "invalid_string_length:\(length)"
        default:
            return "parse_error"
        }
    }
}
