//! Streaming MAVLink v2 byte decoder.
//!
//! Designed for arbitrary-sized chunk arrivals from UDP / serial. Feed bytes
//! into [`Decoder::feed`] and drain [`DecoderEvent`]s. Malformed frames and
//! unknown message ids are surfaced as events; the decoder never returns an
//! `Err` from `feed` itself.

use super::crc::frame_crc;
use super::messages::{crc_extra_for_id, MavlinkMessage};
use super::parse_errors::{ParseErrorKind, ParseErrors};
use super::{HEADER_LEN, INCOMPAT_FLAG_SIGNED, MAVLINK_V2_STX, MAX_PAYLOAD, SIGNATURE_LEN};

#[derive(Debug, Clone, PartialEq)]
pub enum DecoderEvent {
    /// A frame was fully decoded into a typed message.
    Message {
        sysid: u8,
        compid: u8,
        seq: u8,
        message: MavlinkMessage,
    },
    /// A frame was discarded because its CRC did not match.
    Crc { msg_id: u32, seq: u8 },
    /// A frame was discarded because its msgid is outside the §7.7 surface.
    UnknownId { msg_id: u32, seq: u8 },
    /// A frame's payload bytes did not parse cleanly (e.g. invalid enum).
    InvalidPayload {
        msg_id: u32,
        seq: u8,
        reason: &'static str,
    },
    /// Per-link sequence number jumped (logged but the frame is still emitted
    /// in the preceding `Message` event).
    SequenceGap {
        sysid: u8,
        compid: u8,
        expected: u8,
        actual: u8,
    },
}

#[derive(Debug)]
pub struct Decoder {
    buf: Vec<u8>,
    /// Counter snapshot to expose via the codec health surface.
    pub errors: ParseErrors,
    /// Last sequence number per (sysid, compid).
    last_seq: std::collections::HashMap<(u8, u8), u8>,
}

impl Default for Decoder {
    fn default() -> Self {
        Self::new()
    }
}

impl Decoder {
    pub fn new() -> Self {
        Self {
            buf: Vec::with_capacity(4 * 1024),
            errors: ParseErrors::new(),
            last_seq: std::collections::HashMap::new(),
        }
    }

    /// Push raw bytes into the decoder and drain any complete events.
    pub fn feed(&mut self, bytes: &[u8]) -> Vec<DecoderEvent> {
        self.buf.extend_from_slice(bytes);
        self.drain()
    }

    fn drain(&mut self) -> Vec<DecoderEvent> {
        let mut events = Vec::new();
        loop {
            let Some(stx_idx) = self.buf.iter().position(|b| *b == MAVLINK_V2_STX) else {
                // No STX in buffer; discard any garbage we have collected.
                self.buf.clear();
                break;
            };

            if stx_idx > 0 {
                // Skip leading garbage up to the first STX byte.
                self.buf.drain(..stx_idx);
            }

            // After draining, STX is at index 0.
            if self.buf.len() < HEADER_LEN {
                // Not enough for header yet.
                break;
            }

            let payload_len = self.buf[1] as usize;
            let incompat = self.buf[2];
            let seq = self.buf[4];
            let sysid = self.buf[5];
            let compid = self.buf[6];
            let msg_id = u32::from_le_bytes([self.buf[7], self.buf[8], self.buf[9], 0]);

            if payload_len > MAX_PAYLOAD {
                // Malformed length; resync by skipping the STX.
                self.errors.record(ParseErrorKind::InvalidPayload);
                events.push(DecoderEvent::InvalidPayload {
                    msg_id,
                    seq,
                    reason: "payload_len > 255",
                });
                self.buf.drain(..1);
                continue;
            }

            let signature_len = if incompat & INCOMPAT_FLAG_SIGNED != 0 {
                SIGNATURE_LEN
            } else {
                0
            };
            let total_frame = HEADER_LEN + payload_len + 2 + signature_len;

            if self.buf.len() < total_frame {
                // Wait for the rest of this frame.
                break;
            }

            // Verify CRC.
            let body = &self.buf[1..HEADER_LEN + payload_len];
            let frame_crc_bytes = u16::from_le_bytes([
                self.buf[HEADER_LEN + payload_len],
                self.buf[HEADER_LEN + payload_len + 1],
            ]);

            let crc_extra = crc_extra_for_id(msg_id);
            match crc_extra {
                None => {
                    self.errors.record(ParseErrorKind::UnknownId);
                    events.push(DecoderEvent::UnknownId { msg_id, seq });
                    self.buf.drain(..total_frame);
                    continue;
                }
                Some(extra) => {
                    let computed = frame_crc(body, extra);
                    if computed != frame_crc_bytes {
                        self.errors.record(ParseErrorKind::Crc);
                        events.push(DecoderEvent::Crc { msg_id, seq });
                        // Consume the bad frame so we don't loop forever.
                        self.buf.drain(..total_frame);
                        continue;
                    }
                }
            }

            let payload = &self.buf[HEADER_LEN..HEADER_LEN + payload_len];
            match MavlinkMessage::decode(msg_id, payload) {
                Ok(message) => {
                    if let Some(expected) =
                        self.last_seq.insert((sysid, compid), seq.wrapping_add(1))
                    {
                        if expected != seq {
                            self.errors.record(ParseErrorKind::SequenceGap);
                            events.push(DecoderEvent::SequenceGap {
                                sysid,
                                compid,
                                expected,
                                actual: seq,
                            });
                        }
                    }
                    events.push(DecoderEvent::Message {
                        sysid,
                        compid,
                        seq,
                        message,
                    });
                }
                Err(crate::internal::codec::MavlinkParseError::UnknownMessageId(id)) => {
                    self.errors.record(ParseErrorKind::UnknownId);
                    events.push(DecoderEvent::UnknownId { msg_id: id, seq });
                }
                Err(crate::internal::codec::MavlinkParseError::TruncatedPayload { .. }) => {
                    self.errors.record(ParseErrorKind::Truncated);
                    events.push(DecoderEvent::InvalidPayload {
                        msg_id,
                        seq,
                        reason: "payload shorter than message minimum",
                    });
                }
                Err(crate::internal::codec::MavlinkParseError::InvalidPayload {
                    reason, ..
                }) => {
                    self.errors.record(ParseErrorKind::InvalidPayload);
                    events.push(DecoderEvent::InvalidPayload {
                        msg_id,
                        seq,
                        reason,
                    });
                }
                Err(crate::internal::codec::MavlinkParseError::CrcMismatch { .. }) => {
                    // Shouldn't reach here — CRC was verified above.
                    self.errors.record(ParseErrorKind::Crc);
                    events.push(DecoderEvent::Crc { msg_id, seq });
                }
            }

            self.buf.drain(..total_frame);
        }
        events
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::internal::codec::encoder::Encoder;
    use crate::internal::codec::messages::Heartbeat;

    #[test]
    fn round_trips_one_heartbeat() {
        // Arrange
        let enc = Encoder::new(1, 191);
        let msg = MavlinkMessage::Heartbeat(Heartbeat {
            custom_mode: 0,
            mavtype: 2,
            autopilot: 3,
            base_mode: 0,
            system_status: 4,
            mavlink_version: 3,
        });
        let frame = enc.encode(&msg);

        // Act
        let mut dec = Decoder::new();
        let events = dec.feed(&frame);

        // Assert
        assert_eq!(events.len(), 1, "expected one event, got {events:?}");
        let DecoderEvent::Message { message, .. } = &events[0] else {
            panic!("expected Message event, got {:?}", events[0]);
        };
        assert_eq!(*message, msg);
        assert_eq!(dec.errors.snapshot().total(), 0);
    }

    #[test]
    fn rejects_bad_crc() {
        // Arrange
        let enc = Encoder::new(1, 191);
        let msg = MavlinkMessage::Heartbeat(Heartbeat {
            custom_mode: 0,
            mavtype: 2,
            autopilot: 3,
            base_mode: 0,
            system_status: 4,
            mavlink_version: 3,
        });
        let mut frame = enc.encode(&msg);
        let last_idx = frame.len() - 1;
        frame[last_idx] ^= 0xFF;

        // Act
        let mut dec = Decoder::new();
        let events = dec.feed(&frame);

        // Assert
        assert!(events.iter().any(|e| matches!(e, DecoderEvent::Crc { .. })));
        assert_eq!(dec.errors.snapshot().crc, 1);
    }

    #[test]
    fn skips_unknown_message_id() {
        // Arrange: hand-build a frame with msgid 999.
        let body = [
            0x00, // payload_len = 0
            0x00, 0x00, 0x00, 0x01, 0xBE, // incompat, compat, seq, sysid, compid
            0xE7, 0x03, 0x00, // msgid = 999, LE 3 bytes
        ];
        let mut frame = Vec::new();
        frame.push(MAVLINK_V2_STX);
        frame.extend_from_slice(&body);
        // Bogus CRC; the path returns UnknownId before CRC check anyway.
        frame.extend_from_slice(&[0x00, 0x00]);

        // Act
        let mut dec = Decoder::new();
        let events = dec.feed(&frame);

        // Assert
        assert!(events
            .iter()
            .any(|e| matches!(e, DecoderEvent::UnknownId { msg_id: 999, .. })));
        assert_eq!(dec.errors.snapshot().unknown_id, 1);
    }
}
