use std::fmt;

pub const HEADER_SIZE: usize = 10;
pub const MAX_PAYLOAD_SIZE: usize = 0x00ff_ffff;
pub const MAX_BUFFER_SIZE: usize = HEADER_SIZE + MAX_PAYLOAD_SIZE;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Frame {
    pub version: u8,
    pub command: u8,
    pub sequence: u16,
    pub opcode: u16,
    pub compression: u8,
    pub payload: Vec<u8>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FrameError {
    IncompleteHeader,
    IncompleteFrame { expected: usize, actual: usize },
    TrailingBytes { expected: usize, actual: usize },
    PayloadTooLarge { actual: usize, maximum: usize },
    BufferTooLarge { actual: usize, maximum: usize },
}

impl fmt::Display for FrameError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::IncompleteHeader => formatter.write_str("frame header is incomplete"),
            Self::IncompleteFrame { expected, actual } => {
                write!(
                    formatter,
                    "frame is incomplete: expected {expected} bytes, got {actual}"
                )
            }
            Self::TrailingBytes { expected, actual } => {
                write!(
                    formatter,
                    "frame has trailing bytes: expected {expected} bytes, got {actual}"
                )
            }
            Self::PayloadTooLarge { actual, maximum } => {
                write!(
                    formatter,
                    "payload is too large: {actual} bytes exceeds {maximum}"
                )
            }
            Self::BufferTooLarge { actual, maximum } => {
                write!(
                    formatter,
                    "decoder buffer is too large: {actual} bytes exceeds {maximum}"
                )
            }
        }
    }
}

impl std::error::Error for FrameError {}

pub fn encode(frame: &Frame) -> Result<Vec<u8>, FrameError> {
    if frame.payload.len() > MAX_PAYLOAD_SIZE {
        return Err(FrameError::PayloadTooLarge {
            actual: frame.payload.len(),
            maximum: MAX_PAYLOAD_SIZE,
        });
    }

    let mut out = Vec::with_capacity(HEADER_SIZE + frame.payload.len());
    out.push(frame.version);
    out.push(frame.command);
    out.extend_from_slice(&frame.sequence.to_be_bytes());
    out.extend_from_slice(&frame.opcode.to_be_bytes());
    let packed_length = ((frame.compression as u32) << 24) | frame.payload.len() as u32;
    out.extend_from_slice(&packed_length.to_be_bytes());
    out.extend_from_slice(&frame.payload);
    Ok(out)
}

pub fn decode(bytes: &[u8]) -> Result<Frame, FrameError> {
    if bytes.len() < HEADER_SIZE {
        return Err(FrameError::IncompleteHeader);
    }

    let packed_length = u32::from_be_bytes([bytes[6], bytes[7], bytes[8], bytes[9]]);
    let payload_length = (packed_length & 0x00ff_ffff) as usize;
    let expected = HEADER_SIZE + payload_length;
    if bytes.len() < expected {
        return Err(FrameError::IncompleteFrame {
            expected,
            actual: bytes.len(),
        });
    }
    if bytes.len() > expected {
        return Err(FrameError::TrailingBytes {
            expected,
            actual: bytes.len(),
        });
    }

    Ok(Frame {
        version: bytes[0],
        command: bytes[1],
        sequence: u16::from_be_bytes([bytes[2], bytes[3]]),
        opcode: u16::from_be_bytes([bytes[4], bytes[5]]),
        compression: (packed_length >> 24) as u8,
        payload: bytes[HEADER_SIZE..expected].to_vec(),
    })
}

#[derive(Default)]
pub struct FrameDecoder {
    buffer: Vec<u8>,
}

impl FrameDecoder {
    pub fn push(&mut self, bytes: &[u8]) -> Result<Vec<Frame>, FrameError> {
        let next_length = self.buffer.len().saturating_add(bytes.len());
        if next_length > MAX_BUFFER_SIZE {
            self.buffer.clear();
            return Err(FrameError::BufferTooLarge {
                actual: next_length,
                maximum: MAX_BUFFER_SIZE,
            });
        }

        self.buffer.extend_from_slice(bytes);
        let mut frames = Vec::new();
        let mut consumed = 0usize;

        while self.buffer.len().saturating_sub(consumed) >= HEADER_SIZE {
            let header = &self.buffer[consumed..consumed + HEADER_SIZE];
            let packed_length = u32::from_be_bytes([header[6], header[7], header[8], header[9]]);
            let payload_length = (packed_length & 0x00ff_ffff) as usize;
            let total = HEADER_SIZE + payload_length;
            if self.buffer.len().saturating_sub(consumed) < total {
                break;
            }
            frames.push(decode(&self.buffer[consumed..consumed + total])?);
            consumed += total;
        }

        if consumed != 0 {
            self.buffer.drain(..consumed);
        }
        Ok(frames)
    }

    pub fn buffered_len(&self) -> usize {
        self.buffer.len()
    }
}

#[cfg(test)]
mod tests {
    use super::{
        decode, encode, Frame, FrameDecoder, FrameError, MAX_BUFFER_SIZE, MAX_PAYLOAD_SIZE,
    };

    fn sample_frame(sequence: u16, payload: &[u8]) -> Frame {
        Frame {
            version: 10,
            command: 0,
            sequence,
            opcode: 17,
            compression: 0,
            payload: payload.to_vec(),
        }
    }

    #[test]
    fn frame_round_trip_preserves_all_fields() {
        let frame = sample_frame(23, b"synthetic-payload");
        let wire = encode(&frame).unwrap();
        assert_eq!(decode(&wire).unwrap(), frame);
    }

    #[test]
    fn decoder_reassembles_split_input() {
        let wire = encode(&sample_frame(7, b"split")).unwrap();
        let mut decoder = FrameDecoder::default();
        assert!(decoder.push(&wire[..4]).unwrap().is_empty());
        let frames = decoder.push(&wire[4..]).unwrap();
        assert_eq!(frames, vec![sample_frame(7, b"split")]);
        assert_eq!(decoder.buffered_len(), 0);
    }

    #[test]
    fn decoder_returns_multiple_frames() {
        let first = encode(&sample_frame(1, b"one")).unwrap();
        let second = encode(&sample_frame(2, b"two")).unwrap();
        let mut decoder = FrameDecoder::default();
        let frames = decoder.push(&[first, second].concat()).unwrap();
        assert_eq!(
            frames,
            vec![sample_frame(1, b"one"), sample_frame(2, b"two")]
        );
    }

    #[test]
    fn decodes_a_synthetic_protocol_contract_vector() {
        let wire = [10, 0, 0, 2, 0, 6, 0, 0, 0, 3, 0xaa, 0xbb, 0xcc];
        assert_eq!(
            decode(&wire).unwrap(),
            Frame {
                version: 10,
                command: 0,
                sequence: 2,
                opcode: 6,
                compression: 0,
                payload: vec![0xaa, 0xbb, 0xcc],
            }
        );
    }

    #[test]
    fn standalone_decoder_rejects_trailing_bytes() {
        let wire = [10, 0, 0, 2, 0, 6, 0, 0, 0, 1, 0xaa, 0xbb];
        assert_eq!(
            decode(&wire).unwrap_err(),
            FrameError::TrailingBytes {
                expected: 11,
                actual: 12,
            }
        );
    }

    #[test]
    fn encoder_rejects_an_oversized_payload() {
        let payload = vec![0; MAX_PAYLOAD_SIZE + 1];
        let error = encode(&sample_frame(1, &payload)).unwrap_err();
        assert_eq!(
            error,
            FrameError::PayloadTooLarge {
                actual: MAX_PAYLOAD_SIZE + 1,
                maximum: MAX_PAYLOAD_SIZE,
            }
        );
    }

    #[test]
    fn decoder_clears_an_oversized_partial_frame() {
        let mut decoder = FrameDecoder::default();
        let error = decoder.push(&vec![0; MAX_BUFFER_SIZE + 1]).unwrap_err();
        assert_eq!(
            error,
            FrameError::BufferTooLarge {
                actual: MAX_BUFFER_SIZE + 1,
                maximum: MAX_BUFFER_SIZE,
            }
        );
        assert_eq!(decoder.buffered_len(), 0);
    }
}
