use rust_decimal::Decimal;
use serde_json::Value as JsonValue;

use super::error::DecodeError;
use super::value::{DynValue, StructValue};
use crate::{EnumType, Fields, ScalarTyp, SimpleEnumType, StructType, Typ};

/// Decode rapira-encoded bytes into a [`DynValue`] matching the given [`Typ`].
///
/// Expects the entire buffer to be consumed (returns [`DecodeError::TrailingBytes`]
/// otherwise).
pub fn decode(typ: &Typ, bytes: &[u8]) -> Result<DynValue, DecodeError> {
    let mut r = Reader::new(bytes);
    let v = read_value(typ, &mut r)?;
    if r.pos != bytes.len() {
        return Err(DecodeError::TrailingBytes {
            decoded: r.pos,
            total: bytes.len(),
        });
    }
    Ok(v)
}

struct Reader<'a> {
    buf: &'a [u8],
    pos: usize,
}

impl<'a> Reader<'a> {
    fn new(buf: &'a [u8]) -> Self {
        Self { buf, pos: 0 }
    }

    fn take(&mut self, n: usize) -> Result<&'a [u8], DecodeError> {
        let end = self.pos.checked_add(n).ok_or(DecodeError::UnexpectedEof {
            offset: self.pos,
            need: n,
        })?;
        if end > self.buf.len() {
            return Err(DecodeError::UnexpectedEof {
                offset: self.pos,
                need: end - self.buf.len(),
            });
        }
        let slice = &self.buf[self.pos..end];
        self.pos = end;
        Ok(slice)
    }

    fn u8(&mut self) -> Result<u8, DecodeError> {
        Ok(self.take(1)?[0])
    }
    fn u16_le(&mut self) -> Result<u16, DecodeError> {
        let s = self.take(2)?;
        Ok(u16::from_le_bytes([s[0], s[1]]))
    }
    fn u32_le(&mut self) -> Result<u32, DecodeError> {
        let s = self.take(4)?;
        Ok(u32::from_le_bytes([s[0], s[1], s[2], s[3]]))
    }
    fn u64_le(&mut self) -> Result<u64, DecodeError> {
        let s = self.take(8)?;
        let mut arr = [0u8; 8];
        arr.copy_from_slice(s);
        Ok(u64::from_le_bytes(arr))
    }
    fn u64_be(&mut self) -> Result<u64, DecodeError> {
        let s = self.take(8)?;
        let mut arr = [0u8; 8];
        arr.copy_from_slice(s);
        Ok(u64::from_be_bytes(arr))
    }
    fn i32_le(&mut self) -> Result<i32, DecodeError> {
        Ok(self.u32_le()? as i32)
    }
    fn i64_le(&mut self) -> Result<i64, DecodeError> {
        Ok(self.u64_le()? as i64)
    }
    fn f32_le(&mut self) -> Result<f32, DecodeError> {
        Ok(f32::from_bits(self.u32_le()?))
    }
    fn f64_le(&mut self) -> Result<f64, DecodeError> {
        Ok(f64::from_bits(self.u64_le()?))
    }

    fn len_prefixed_bytes(&mut self) -> Result<Vec<u8>, DecodeError> {
        let n = self.u32_le()? as usize;
        Ok(self.take(n)?.to_vec())
    }
}

fn read_value(typ: &Typ, r: &mut Reader<'_>) -> Result<DynValue, DecodeError> {
    Ok(match typ {
        Typ::Scalar(scalar) => match scalar {
            ScalarTyp::Void => DynValue::Void,
            ScalarTyp::Bool => DynValue::Bool(r.u8()? != 0),
            ScalarTyp::U8 => DynValue::U8(r.u8()?),
            ScalarTyp::U16 => DynValue::U16(r.u16_le()?),
            ScalarTyp::U32 => DynValue::U32(r.u32_le()?),
            ScalarTyp::U64 => DynValue::U64(r.u64_le()?),
            ScalarTyp::I32 => DynValue::I32(r.i32_le()?),
            ScalarTyp::I64 => DynValue::I64(r.i64_le()?),
            ScalarTyp::F32 => DynValue::F32(r.f32_le()?),
            ScalarTyp::F64 => DynValue::F64(r.f64_le()?),
            ScalarTyp::Str => {
                let bytes = r.len_prefixed_bytes()?;
                let s = String::from_utf8(bytes)
                    .map_err(|_| DecodeError::InvalidUtf8 { offset: r.pos })?;
                DynValue::Str(s)
            }
            ScalarTyp::Datetime => DynValue::Datetime(r.i64_le()?),
            ScalarTyp::Timestamp => DynValue::Timestamp(r.u64_le()?),
            ScalarTyp::Decimal => {
                let slice = r.take(16)?;
                let mut arr = [0u8; 16];
                arr.copy_from_slice(slice);
                DynValue::Decimal(Decimal::deserialize(arr))
            }
            ScalarTyp::Id32 => DynValue::Id32(r.u32_le()?),
            ScalarTyp::Id64 => DynValue::Id64(r.u64_be()?),
            ScalarTyp::Fuid => DynValue::Fuid(r.u64_be()?),
            ScalarTyp::LowId => DynValue::LowId(r.u32_le()?),
            ScalarTyp::Bytes => DynValue::Bytes(r.len_prefixed_bytes()?),
            ScalarTyp::ArrayBytes(n) => DynValue::ArrayBytes(r.take(*n as usize)?.to_vec()),
            ScalarTyp::RustJson => {
                let raw = r.len_prefixed_bytes()?;
                let v: JsonValue = serde_json::from_slice(&raw)
                    .map_err(|_| DecodeError::InvalidJson { offset: r.pos })?;
                DynValue::RustJson(v)
            }
            ScalarTyp::JsonBytes => {
                let bytes = r.len_prefixed_bytes()?;
                let s = String::from_utf8(bytes)
                    .map_err(|_| DecodeError::InvalidUtf8 { offset: r.pos })?;
                DynValue::JsonBytes(s)
            }
        },
        Typ::Array(n, inner) => {
            let mut out = Vec::with_capacity(*n as usize);
            for _ in 0..*n {
                out.push(read_value(inner, r)?);
            }
            DynValue::Array(out)
        }
        Typ::Vec(inner) => {
            let n = r.u32_le()? as usize;
            let mut out = Vec::with_capacity(n);
            for _ in 0..n {
                out.push(read_value(inner, r)?);
            }
            DynValue::Vec(out)
        }
        Typ::Optional(inner) => {
            if r.u8()? == 0 {
                DynValue::Optional(None)
            } else {
                DynValue::Optional(Some(Box::new(read_value(inner, r)?)))
            }
        }
        Typ::SimpleEnum(SimpleEnumType { name, variants }) => {
            let offset = r.pos;
            let tag = r.u8()?;
            let variant_name = variants
                .iter()
                .find(|(t, _)| *t == tag)
                .map(|(_, n)| *n)
                .ok_or(DecodeError::UnknownVariant { tag, offset })?;
            DynValue::SimpleEnum {
                name,
                variant: tag,
                variant_name,
            }
        }
        Typ::Struct(StructType { name, fields }) => {
            let fv = match fields {
                Fields::Named(arr) => {
                    let mut pairs = Vec::with_capacity(arr.len());
                    for (field_name, field_typ) in *arr {
                        pairs.push((*field_name, read_value(field_typ, r)?));
                    }
                    StructValue::Named(pairs)
                }
                Fields::Unnamed(arr) => {
                    let mut items = Vec::with_capacity(arr.len());
                    for field_typ in *arr {
                        items.push(read_value(field_typ, r)?);
                    }
                    StructValue::Unnamed(items)
                }
            };
            DynValue::Struct { name, fields: fv }
        }
        Typ::Enum(EnumType { name, variants }) => {
            let offset = r.pos;
            let tag = r.u8()?;
            let (_, (variant_name, variant_typ)) = variants
                .iter()
                .find(|(t, _)| *t == tag)
                .ok_or(DecodeError::UnknownVariant { tag, offset })?;
            let field = Box::new(read_value(variant_typ, r)?);
            DynValue::Enum {
                name,
                variant: tag,
                variant_name,
                field,
            }
        }
        Typ::Custom(_, _) => return Err(DecodeError::CustomUnsupported),
    })
}
