//! Primitive type implementations

use std::borrow::Cow;
use std::io::{Read, Write};

use crate::error::{ReadError, WriteError};
use crate::traits::ClickHouseType;

// Macro to implement ClickHouseType for primitive numeric types
macro_rules! impl_primitive {
    ($rust:ty, $ch_name:expr, $size:expr) => {
        impl ClickHouseType for $rust {
            fn type_name() -> Cow<'static, str> {
                Cow::Borrowed($ch_name)
            }

            fn write_binary<W: Write>(&self, writer: &mut W) -> Result<(), WriteError> {
                writer.write_all(&self.to_le_bytes())?;
                Ok(())
            }

            fn read_binary<R: Read>(reader: &mut R) -> Result<Self, ReadError> {
                let mut buf = [0u8; $size];
                reader.read_exact(&mut buf).map_err(|_| ReadError::UnexpectedEof)?;
                Ok(Self::from_le_bytes(buf))
            }

            fn fixed_size() -> Option<usize> {
                Some($size)
            }
        }
    };
}

// Integer types
impl_primitive!(i8, "Int8", 1);
impl_primitive!(i16, "Int16", 2);
impl_primitive!(i32, "Int32", 4);
impl_primitive!(i64, "Int64", 8);
impl_primitive!(i128, "Int128", 16);

impl_primitive!(u8, "UInt8", 1);
impl_primitive!(u16, "UInt16", 2);
impl_primitive!(u32, "UInt32", 4);
impl_primitive!(u64, "UInt64", 8);
impl_primitive!(u128, "UInt128", 16);

// Float types
impl_primitive!(f32, "Float32", 4);
impl_primitive!(f64, "Float64", 8);

// Boolean
impl ClickHouseType for bool {
    fn type_name() -> Cow<'static, str> {
        Cow::Borrowed("Bool")
    }

    fn write_binary<W: Write>(&self, writer: &mut W) -> Result<(), WriteError> {
        writer.write_all(&[*self as u8])?;
        Ok(())
    }

    fn read_binary<R: Read>(reader: &mut R) -> Result<Self, ReadError> {
        let mut buf = [0u8; 1];
        reader.read_exact(&mut buf).map_err(|_| ReadError::UnexpectedEof)?;
        Ok(buf[0] != 0)
    }

    fn fixed_size() -> Option<usize> {
        Some(1)
    }
}

// String
impl ClickHouseType for String {
    fn type_name() -> Cow<'static, str> {
        Cow::Borrowed("String")
    }

    fn write_binary<W: Write>(&self, writer: &mut W) -> Result<(), WriteError> {
        // Write length as LEB128
        write_leb128(writer, self.len() as u64)?;
        writer.write_all(self.as_bytes())?;
        Ok(())
    }

    fn read_binary<R: Read>(reader: &mut R) -> Result<Self, ReadError> {
        let len = read_leb128(reader)? as usize;
        let mut buf = vec![0u8; len];
        reader.read_exact(&mut buf).map_err(|_| ReadError::UnexpectedEof)?;
        String::from_utf8(buf).map_err(ReadError::from)
    }

    fn to_sql_literal(&self) -> String {
        format!("'{}'", self.replace('\'', "\\'"))
    }
}

// UUID
impl ClickHouseType for uuid::Uuid {
    fn type_name() -> Cow<'static, str> {
        Cow::Borrowed("UUID")
    }

    fn write_binary<W: Write>(&self, writer: &mut W) -> Result<(), WriteError> {
        writer.write_all(self.as_bytes())?;
        Ok(())
    }

    fn read_binary<R: Read>(reader: &mut R) -> Result<Self, ReadError> {
        let mut buf = [0u8; 16];
        reader.read_exact(&mut buf).map_err(|_| ReadError::UnexpectedEof)?;
        Ok(uuid::Uuid::from_bytes(buf))
    }

    fn fixed_size() -> Option<usize> {
        Some(16)
    }

    fn to_sql_literal(&self) -> String {
        format!("'{}'", self)
    }
}

// Helper functions for LEB128 encoding (used by String and Array)
pub(crate) fn write_leb128<W: Write>(writer: &mut W, mut value: u64) -> Result<(), WriteError> {
    loop {
        let mut byte = (value & 0x7F) as u8;
        value >>= 7;
        if value != 0 {
            byte |= 0x80;
        }
        writer.write_all(&[byte])?;
        if value == 0 {
            break;
        }
    }
    Ok(())
}

pub(crate) fn read_leb128<R: Read>(reader: &mut R) -> Result<u64, ReadError> {
    let mut result = 0u64;
    let mut shift = 0;
    loop {
        let mut buf = [0u8; 1];
        reader.read_exact(&mut buf).map_err(|_| ReadError::UnexpectedEof)?;
        let byte = buf[0];
        result |= ((byte & 0x7F) as u64) << shift;
        if byte & 0x80 == 0 {
            break;
        }
        shift += 7;
        if shift >= 64 {
            return Err(ReadError::InvalidFormat("LEB128 overflow".to_string()));
        }
    }
    Ok(result)
}

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

    #[test]
    fn test_leb128_roundtrip() {
        let test_values = [0u64, 1, 127, 128, 255, 256, 65535, 65536, u64::MAX];
        for &value in &test_values {
            let mut buf = Vec::new();
            write_leb128(&mut buf, value).unwrap();
            let result = read_leb128(&mut buf.as_slice()).unwrap();
            assert_eq!(value, result);
        }
    }
}
