//! Bounded Binary LLSD codec compatible with `LibreMetaverse`'s marker grammar.

#![allow(clippy::missing_errors_doc)] // Public Result shapes are fixed by the mapped API.
#![allow(clippy::needless_pass_by_value)] // Owned arguments mirror mapped C# value parameters.
#![allow(clippy::unnecessary_wraps)] // Result return shapes are fixed by the mapped API.
#![allow(clippy::unused_self)] // Parser error helpers retain local cursor context call sites.

use crate::{Error, OSD};
use libremetaverse_types::UUID;
use libremetaverse_types::compat::{ReadWrite, Uri};
use std::collections::HashMap;
use std::io::{Cursor, Read as _, Seek as _, SeekFrom};
use std::time::{Duration, UNIX_EPOCH};

const HEADER: &[u8] = b"<?llsd/binary?>";
const ALT_HEADER: &[u8] = b"<? llsd/binary ?>";
const MAX_INPUT_BYTES: usize = OSD::DEFAULT_MAX_BINARY_BYTES;

const fn is_binary_whitespace(byte: u8) -> bool {
    matches!(byte, b' ' | b'\t' | b'\n' | b'\r')
}

pub(crate) fn deserialize_bytes(data: Vec<u8>) -> Result<OSD, Error> {
    deserialize_prefix(&data).map(|(value, _)| value)
}

pub(crate) fn deserialize_prefix(data: &[u8]) -> Result<(OSD, usize), Error> {
    if data.len() > MAX_INPUT_BYTES {
        return Err(parse_error(0, "binary LLSD input exceeds allocation limit"));
    }
    let mut parser = Parser::new(data);
    parser.skip_whitespace();
    if parser.consume_prefix_ignore_ascii_case(ALT_HEADER)
        || parser.consume_prefix_ignore_ascii_case(HEADER)
    {
        parser.skip_whitespace();
    }
    let value = parser.parse_value(0)?;
    value.validate_limits(
        OSD::DEFAULT_MAX_DEPTH,
        OSD::DEFAULT_MAX_NODES,
        OSD::DEFAULT_MAX_BINARY_BYTES,
    )?;
    Ok((value, parser.position))
}

pub(crate) fn deserialize_stream(mut stream: Box<dyn ReadWrite + Send>) -> Result<OSD, Error> {
    let mut data = Vec::new();
    (&mut *stream)
        .take((MAX_INPUT_BYTES + 1) as u64)
        .read_to_end(&mut data)
        .map_err(|_| Error::InvalidOperation)?;
    deserialize_bytes(data)
}

pub(crate) fn serialize(value: OSD) -> Result<Vec<u8>, Error> {
    serialize_with_header(value, true)
}

pub(crate) fn serialize_with_header(value: OSD, prepend_header: bool) -> Result<Vec<u8>, Error> {
    value.validate_limits(
        OSD::DEFAULT_MAX_DEPTH,
        OSD::DEFAULT_MAX_NODES,
        OSD::DEFAULT_MAX_BINARY_BYTES,
    )?;
    let mut encoder = Encoder::new();
    if prepend_header {
        encoder.extend(HEADER)?;
        encoder.push(b'\n')?;
    }
    encoder.write_value(&value, 0)?;
    Ok(encoder.finish())
}

pub(crate) fn serialize_stream(value: OSD) -> Result<Cursor<Vec<u8>>, Error> {
    serialize_stream_with_header(value, true)
}

pub(crate) fn serialize_stream_with_header(
    value: OSD,
    prepend_header: bool,
) -> Result<Cursor<Vec<u8>>, Error> {
    let bytes = serialize_with_header(value, prepend_header)?;
    let position = bytes.len() as u64;
    let mut cursor = Cursor::new(bytes);
    cursor.set_position(position);
    Ok(cursor)
}

pub(crate) fn consume_bytes(
    mut stream: Box<dyn ReadWrite + Send>,
    consume_bytes: i32,
) -> Result<Vec<u8>, Error> {
    let length = usize::try_from(consume_bytes).map_err(|_| Error::Argument)?;
    if length > MAX_INPUT_BYTES {
        return Err(Error::Argument);
    }
    let mut bytes = vec![0_u8; length];
    stream
        .read_exact(&mut bytes)
        .map_err(|_| Error::IndexOutOfRange)?;
    Ok(bytes)
}

pub(crate) fn find_byte(mut stream: Box<dyn ReadWrite + Send>, to_find: u8) -> Result<bool, Error> {
    let start = stream
        .stream_position()
        .map_err(|_| Error::InvalidOperation)?;
    let mut byte = [0_u8; 1];
    if stream
        .read(&mut byte)
        .map_err(|_| Error::InvalidOperation)?
        == 0
    {
        return Ok(false);
    }
    if byte[0] == to_find {
        Ok(true)
    } else {
        stream
            .seek(SeekFrom::Start(start))
            .map_err(|_| Error::InvalidOperation)?;
        Ok(false)
    }
}

pub(crate) fn find_string(
    mut stream: Box<dyn ReadWrite + Send>,
    to_find: String,
) -> Result<bool, Error> {
    let start = stream
        .stream_position()
        .map_err(|_| Error::InvalidOperation)?;
    let expected = to_find.as_bytes();
    if expected.len() > MAX_INPUT_BYTES {
        return Err(Error::Argument);
    }
    let mut actual = vec![0_u8; expected.len()];
    let matched = stream.read_exact(&mut actual).is_ok()
        && actual
            .iter()
            .zip(expected)
            .all(|(left, right)| left.eq_ignore_ascii_case(right));
    if !matched {
        stream
            .seek(SeekFrom::Start(start))
            .map_err(|_| Error::InvalidOperation)?;
    }
    Ok(matched)
}

pub(crate) fn skip_whitespace(mut stream: Box<dyn ReadWrite + Send>) -> Result<(), Error> {
    loop {
        let start = stream
            .stream_position()
            .map_err(|_| Error::InvalidOperation)?;
        let mut byte = [0_u8; 1];
        if stream
            .read(&mut byte)
            .map_err(|_| Error::InvalidOperation)?
            == 0
        {
            return Ok(());
        }
        if !is_binary_whitespace(byte[0]) {
            stream
                .seek(SeekFrom::Start(start))
                .map_err(|_| Error::InvalidOperation)?;
            return Ok(());
        }
    }
}

pub(crate) fn host_to_network_int_bytes(value: i32) -> Result<Vec<u8>, Error> {
    Ok(value.to_be_bytes().to_vec())
}

pub(crate) fn network_to_host_int(bytes: Vec<u8>) -> Result<i32, Error> {
    let bytes: [u8; 4] = bytes
        .get(..4)
        .ok_or(Error::IndexOutOfRange)?
        .try_into()
        .map_err(|_| Error::IndexOutOfRange)?;
    Ok(i32::from_be_bytes(bytes))
}

pub(crate) fn network_to_host_double(bytes: Vec<u8>) -> Result<f64, Error> {
    let bytes: [u8; 8] = bytes
        .get(..8)
        .ok_or(Error::IndexOutOfRange)?
        .try_into()
        .map_err(|_| Error::IndexOutOfRange)?;
    Ok(f64::from_be_bytes(bytes))
}

struct Parser<'a> {
    bytes: &'a [u8],
    position: usize,
    nodes: usize,
    allocated: usize,
}

impl<'a> Parser<'a> {
    const fn new(bytes: &'a [u8]) -> Self {
        Self {
            bytes,
            position: 0,
            nodes: 0,
            allocated: 0,
        }
    }

    fn parse_value(&mut self, depth: usize) -> Result<OSD, Error> {
        if depth > OSD::DEFAULT_MAX_DEPTH {
            return Err(self.error("binary LLSD nesting depth exceeded"));
        }
        self.nodes = self
            .nodes
            .checked_add(1)
            .ok_or_else(|| self.error("binary LLSD node count overflow"))?;
        if self.nodes > OSD::DEFAULT_MAX_NODES {
            return Err(self.error("binary LLSD node limit exceeded"));
        }
        self.skip_whitespace();
        let marker_position = self.position;
        let marker = self.read_byte("missing binary LLSD value marker")?;
        match marker {
            b'!' => Ok(OSD::Undefined),
            b'1' => Ok(OSD::Boolean(true)),
            b'0' => Ok(OSD::Boolean(false)),
            b'i' => Ok(OSD::Integer(i32::from_be_bytes(
                self.read_array("truncated binary LLSD integer")?,
            ))),
            b'r' => Ok(OSD::Real(f64::from_be_bytes(
                self.read_array("truncated binary LLSD real")?,
            ))),
            b'u' => {
                let bytes = self.read_exact(16, "truncated binary LLSD UUID")?;
                Ok(OSD::UUID(UUID::new_with_bytes_int32(bytes.to_vec(), 0)?))
            }
            b'b' => {
                let length = self.read_length("invalid binary LLSD binary length")?;
                self.add_allocation(length, "binary LLSD binary allocation limit exceeded")?;
                Ok(OSD::Binary(
                    self.read_exact(length, "truncated binary LLSD binary value")?
                        .to_vec(),
                ))
            }
            b's' => {
                let bytes = self.read_sized_text("string")?;
                Ok(OSD::String(String::from_utf8_lossy(bytes).into_owned()))
            }
            b'l' => {
                let bytes = self.read_sized_text("URI")?;
                let text = String::from_utf8_lossy(bytes).into_owned();
                let uri = OSD::String(text.clone())
                    .as_uri()?
                    .ok_or_else(|| self.error_at(marker_position, "invalid binary LLSD URI"))?;
                Ok(OSD::Uri(uri))
            }
            b'd' => {
                let timestamp = f64::from_le_bytes(self.read_array("truncated binary LLSD date")?);
                Ok(OSD::Date(system_time_from_seconds(timestamp).ok_or_else(
                    || self.error_at(marker_position, "invalid binary LLSD date"),
                )?))
            }
            b'[' => self.parse_array(depth),
            b'{' => self.parse_map(depth),
            _ => Err(self.error_at(marker_position, "unknown binary LLSD type marker")),
        }
    }

    fn parse_array(&mut self, depth: usize) -> Result<OSD, Error> {
        let count = self.read_length("invalid binary LLSD array count")?;
        if count > OSD::DEFAULT_MAX_NODES || count > self.remaining() {
            return Err(self.error("binary LLSD array count exceeds available input"));
        }
        let allocation = count
            .checked_mul(std::mem::size_of::<OSD>())
            .ok_or_else(|| self.error("binary LLSD array allocation overflow"))?;
        self.add_allocation(allocation, "binary LLSD array allocation limit exceeded")?;
        let mut values = Vec::with_capacity(count);
        for _ in 0..count {
            values.push(self.parse_value(depth + 1)?);
        }
        self.expect_byte(b']', "missing binary LLSD array end marker")?;
        Ok(OSD::Array(values))
    }

    fn parse_map(&mut self, depth: usize) -> Result<OSD, Error> {
        let count = self.read_length("invalid binary LLSD map count")?;
        if count > OSD::DEFAULT_MAX_NODES || count > self.remaining() {
            return Err(self.error("binary LLSD map count exceeds available input"));
        }
        let allocation = count
            .checked_mul(std::mem::size_of::<(String, OSD)>())
            .ok_or_else(|| self.error("binary LLSD map allocation overflow"))?;
        self.add_allocation(allocation, "binary LLSD map allocation limit exceeded")?;
        let mut values = HashMap::with_capacity(count);
        for _ in 0..count {
            self.expect_byte(b'k', "missing binary LLSD map key marker")?;
            let key_bytes = self.read_sized_text("map key")?;
            let key = String::from_utf8_lossy(key_bytes).into_owned();
            let value = self.parse_value(depth + 1)?;
            values.insert(key, value);
        }
        self.expect_byte(b'}', "missing binary LLSD map end marker")?;
        Ok(OSD::Map(values))
    }

    fn read_sized_text(&mut self, context: &'static str) -> Result<&'a [u8], Error> {
        let length = self.read_length(match context {
            "string" => "invalid binary LLSD string length",
            "URI" => "invalid binary LLSD URI length",
            _ => "invalid binary LLSD map key length",
        })?;
        self.add_allocation(length, "binary LLSD text allocation limit exceeded")?;
        self.read_exact(
            length,
            match context {
                "string" => "truncated binary LLSD string",
                "URI" => "truncated binary LLSD URI",
                _ => "truncated binary LLSD map key",
            },
        )
    }

    fn read_length(&mut self, context: &'static str) -> Result<usize, Error> {
        let position = self.position;
        let raw = i32::from_be_bytes(self.read_array(context)?);
        usize::try_from(raw).map_err(|_| self.error_at(position, context))
    }

    fn read_array<const N: usize>(&mut self, context: &'static str) -> Result<[u8; N], Error> {
        self.read_exact(N, context)?
            .try_into()
            .map_err(|_| self.error(context))
    }

    fn read_exact(&mut self, length: usize, context: &'static str) -> Result<&'a [u8], Error> {
        let end = self
            .position
            .checked_add(length)
            .ok_or_else(|| self.error(context))?;
        let value = self
            .bytes
            .get(self.position..end)
            .ok_or_else(|| self.error(context))?;
        self.position = end;
        Ok(value)
    }

    fn read_byte(&mut self, context: &'static str) -> Result<u8, Error> {
        let byte = *self
            .bytes
            .get(self.position)
            .ok_or_else(|| self.error(context))?;
        self.position += 1;
        Ok(byte)
    }

    fn expect_byte(&mut self, expected: u8, context: &'static str) -> Result<(), Error> {
        let position = self.position;
        if self.read_byte(context)? == expected {
            Ok(())
        } else {
            Err(self.error_at(position, context))
        }
    }

    fn add_allocation(&mut self, amount: usize, context: &'static str) -> Result<(), Error> {
        self.allocated = self
            .allocated
            .checked_add(amount)
            .ok_or_else(|| self.error(context))?;
        if self.allocated > OSD::DEFAULT_MAX_BINARY_BYTES {
            Err(self.error(context))
        } else {
            Ok(())
        }
    }

    fn skip_whitespace(&mut self) {
        while self
            .bytes
            .get(self.position)
            .is_some_and(|byte| is_binary_whitespace(*byte))
        {
            self.position += 1;
        }
    }

    fn consume_prefix_ignore_ascii_case(&mut self, prefix: &[u8]) -> bool {
        let Some(candidate) = self.bytes.get(self.position..self.position + prefix.len()) else {
            return false;
        };
        if candidate.eq_ignore_ascii_case(prefix) {
            self.position += prefix.len();
            true
        } else {
            false
        }
    }

    const fn remaining(&self) -> usize {
        self.bytes.len() - self.position
    }

    const fn error(&self, context: &'static str) -> Error {
        self.error_at(self.position, context)
    }

    const fn error_at(&self, position: usize, context: &'static str) -> Error {
        parse_error(position, context)
    }
}

struct Encoder {
    bytes: Vec<u8>,
}

impl Encoder {
    fn new() -> Self {
        Self {
            bytes: Vec::with_capacity(128),
        }
    }

    fn finish(self) -> Vec<u8> {
        self.bytes
    }

    fn write_value(&mut self, value: &OSD, depth: usize) -> Result<(), Error> {
        if depth > OSD::DEFAULT_MAX_DEPTH {
            return Err(parse_error(
                self.bytes.len(),
                "binary LLSD nesting depth exceeded",
            ));
        }
        match value {
            OSD::Undefined => self.push(b'!'),
            OSD::Boolean(value) => self.push(if *value { b'1' } else { b'0' }),
            OSD::Integer(value) => {
                self.push(b'i')?;
                self.extend(&value.to_be_bytes())
            }
            OSD::Real(value) => {
                self.push(b'r')?;
                self.extend(&value.to_be_bytes())
            }
            OSD::UUID(value) => {
                self.push(b'u')?;
                self.extend(&value.get_bytes()?)
            }
            OSD::String(value) => {
                self.push(b's')?;
                self.write_sized(value.as_bytes())
            }
            OSD::Binary(value) => {
                self.push(b'b')?;
                self.write_sized(value)
            }
            OSD::Date(value) => {
                self.push(b'd')?;
                self.extend(&crate::model::unix_seconds_for_codec(*value).to_le_bytes())
            }
            OSD::Uri(Uri(value)) => {
                self.push(b'l')?;
                self.write_sized(value.as_bytes())
            }
            OSD::Array(values) => {
                self.push(b'[')?;
                self.write_length(values.len())?;
                for value in values {
                    self.write_value(value, depth + 1)?;
                }
                self.push(b']')
            }
            OSD::Map(values) => {
                self.push(b'{')?;
                self.write_length(values.len())?;
                let mut entries: Vec<_> = values.iter().collect();
                entries.sort_unstable_by_key(|(key, _)| *key);
                for (key, value) in entries {
                    self.push(b'k')?;
                    self.write_sized(key.as_bytes())?;
                    self.write_value(value, depth + 1)?;
                }
                self.push(b'}')
            }
            OSD::LlsdXml(_) => Err(parse_error(
                self.bytes.len(),
                "LLSD XML fragments have no Binary LLSD marker",
            )),
        }
    }

    fn write_sized(&mut self, value: &[u8]) -> Result<(), Error> {
        self.write_length(value.len())?;
        self.extend(value)
    }

    fn write_length(&mut self, length: usize) -> Result<(), Error> {
        let length = i32::try_from(length).map_err(|_| Error::Argument)?;
        self.extend(&length.to_be_bytes())
    }

    fn push(&mut self, value: u8) -> Result<(), Error> {
        if self.bytes.len() >= MAX_INPUT_BYTES {
            return Err(Error::Argument);
        }
        self.bytes.push(value);
        Ok(())
    }

    fn extend(&mut self, value: &[u8]) -> Result<(), Error> {
        let length = self
            .bytes
            .len()
            .checked_add(value.len())
            .ok_or(Error::Argument)?;
        if length > MAX_INPUT_BYTES {
            return Err(Error::Argument);
        }
        self.bytes.extend_from_slice(value);
        Ok(())
    }
}

fn system_time_from_seconds(seconds: f64) -> Option<std::time::SystemTime> {
    if !seconds.is_finite() {
        return None;
    }
    if seconds >= 0.0 {
        UNIX_EPOCH.checked_add(Duration::try_from_secs_f64(seconds).ok()?)
    } else {
        UNIX_EPOCH.checked_sub(Duration::try_from_secs_f64(-seconds).ok()?)
    }
}

const fn parse_error(position: usize, context: &'static str) -> Error {
    Error::Parse { position, context }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn every_variant_round_trips_with_header_and_nested_values() {
        let uuid = UUID::new_with_string("97f4aeca-88a1-42a1-b385-b97b18abb255".into()).unwrap();
        let value = OSD::Array(vec![
            OSD::Undefined,
            OSD::Boolean(true),
            OSD::Integer(-42),
            OSD::Real(12.5),
            OSD::String("text 𐄷".into()),
            OSD::UUID(uuid),
            OSD::Date(UNIX_EPOCH + Duration::from_millis(1_199_218_231_125)),
            OSD::Uri(Uri("https://example.test/a".into())),
            OSD::Binary(vec![0, 1, 255]),
            OSD::Map(HashMap::from([("nested".into(), OSD::Array(vec![]))])),
        ]);
        let encoded = serialize(value.clone()).unwrap();
        assert!(encoded.starts_with(b"<?llsd/binary?>\n"));
        assert_eq!(deserialize_bytes(encoded).unwrap(), value);
        assert_eq!(
            deserialize_bytes(b" \t\r\n<? LlSd/BiNaRy ?>\r\ni\0\0\0\x07".to_vec()).unwrap(),
            OSD::Integer(7)
        );
        assert!(matches!(
            deserialize_bytes(b"\x0b!".to_vec()),
            Err(Error::Parse {
                position: 0,
                context: "unknown binary LLSD type marker"
            })
        ));

        let mut encoded_stream = serialize_stream_with_header(value.clone(), false).unwrap();
        assert_eq!(
            encoded_stream.position(),
            encoded_stream.get_ref().len() as u64
        );
        encoded_stream.set_position(0);
        assert_eq!(deserialize_stream(Box::new(encoded_stream)).unwrap(), value);

        assert_eq!(
            host_to_network_int_bytes(-42).unwrap(),
            (-42_i32).to_be_bytes()
        );
        assert_eq!(
            network_to_host_int((-42_i32).to_be_bytes().to_vec()).unwrap(),
            -42
        );
        assert_eq!(
            network_to_host_double(12.5_f64.to_be_bytes().to_vec())
                .unwrap()
                .to_bits(),
            12.5_f64.to_bits()
        );
    }

    #[test]
    fn prefix_decode_reports_exact_payload_boundary() {
        let value = OSD::Map(HashMap::from([("offset".into(), OSD::Integer(7))]));
        let encoded = serialize_with_header(value.clone(), false).unwrap();
        let mut container = encoded.clone();
        container.extend_from_slice(b"\x78\x9c appended payload");
        let (decoded, consumed) = deserialize_prefix(&container).unwrap();
        assert_eq!(decoded, value);
        assert_eq!(consumed, encoded.len());
        assert_eq!(&container[consumed..], b"\x78\x9c appended payload");
    }

    #[test]
    fn malformed_seed_corpus_is_rejected_with_positions() {
        let expected: &[(&str, &str)] = &[
            ("empty", "missing binary LLSD value marker"),
            ("unknown_marker", "unknown binary LLSD type marker"),
            ("truncated_integer", "truncated binary LLSD integer"),
            ("truncated_real", "truncated binary LLSD real"),
            ("truncated_uuid", "truncated binary LLSD UUID"),
            (
                "negative_binary_length",
                "invalid binary LLSD binary length",
            ),
            (
                "truncated_binary_length",
                "invalid binary LLSD binary length",
            ),
            (
                "negative_string_length",
                "invalid binary LLSD string length",
            ),
            ("negative_array_count", "invalid binary LLSD array count"),
            ("negative_map_count", "invalid binary LLSD map count"),
            (
                "oversized_string_length",
                "binary LLSD text allocation limit exceeded",
            ),
            ("missing_array_end", "missing binary LLSD array end marker"),
            (
                "missing_map_key_marker",
                "missing binary LLSD map key marker",
            ),
            ("missing_map_end", "missing binary LLSD map end marker"),
            ("non_finite_date", "invalid binary LLSD date"),
        ];
        let corpus = include_str!("../../../fuzz/corpus/binary_llsd/malformed.hex");
        let seeds: HashMap<_, _> = corpus
            .lines()
            .map(|line| {
                let (name, hex) = line.split_once(':').expect("named fuzz seed");
                (name, decode_hex(hex))
            })
            .collect();
        assert_eq!(seeds.len(), expected.len());
        for (name, expected_context) in expected {
            let seed = seeds.get(name).expect("fuzz seed named by test");
            let Error::Parse { position, context } = deserialize_bytes(seed.clone()).unwrap_err()
            else {
                panic!("malformed seed did not return a positional parse error");
            };
            assert!(position <= seed.len());
            assert_eq!(context, *expected_context);
        }
    }

    fn decode_hex(value: &str) -> Vec<u8> {
        assert_eq!(value.len() % 2, 0, "fuzz seed hex must contain byte pairs");
        value
            .as_bytes()
            .chunks_exact(2)
            .map(|pair| {
                let text = std::str::from_utf8(pair).expect("ASCII fuzz seed hex");
                u8::from_str_radix(text, 16).expect("valid fuzz seed hex")
            })
            .collect()
    }

    #[test]
    fn excessive_depth_is_rejected_before_stack_growth() {
        let mut bytes = Vec::new();
        for _ in 0..=OSD::DEFAULT_MAX_DEPTH {
            bytes.extend_from_slice(b"[\0\0\0\x01");
        }
        bytes.push(b'!');
        bytes.extend(std::iter::repeat_n(b']', OSD::DEFAULT_MAX_DEPTH + 1));
        assert!(matches!(
            deserialize_bytes(bytes),
            Err(Error::Parse {
                context: "binary LLSD nesting depth exceeded",
                ..
            })
        ));
    }
}
