//! Complex type implementations

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

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

/// Nullable wrapper for ClickHouse Nullable(T) type
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Nullable<T>(pub Option<T>);

impl<T> Nullable<T> {
    /// Create a NULL value
    pub fn null() -> Self {
        Self(None)
    }

    /// Create a non-NULL value
    pub fn some(value: T) -> Self {
        Self(Some(value))
    }

    /// Check if the value is NULL
    pub fn is_null(&self) -> bool {
        self.0.is_none()
    }

    /// Get the inner value, if present
    pub fn as_ref(&self) -> Option<&T> {
        self.0.as_ref()
    }
}

impl<T: ClickHouseType> ClickHouseType for Nullable<T> {
    fn type_name() -> Cow<'static, str> {
        Cow::Owned(format!("Nullable({})", T::type_name()))
    }

    fn write_binary<W: Write>(&self, writer: &mut W) -> Result<(), WriteError> {
        match &self.0 {
            None => writer.write_all(&[1])?,  // NULL marker
            Some(val) => {
                writer.write_all(&[0])?;  // NOT NULL marker
                val.write_binary(writer)?;
            }
        }
        Ok(())
    }

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

        if marker[0] == 1 {
            Ok(Nullable(None))
        } else {
            Ok(Nullable(Some(T::read_binary(reader)?)))
        }
    }

    fn is_nullable() -> bool {
        true
    }
}

impl<T> From<Option<T>> for Nullable<T> {
    fn from(opt: Option<T>) -> Self {
        Self(opt)
    }
}

impl<T> From<T> for Nullable<T> {
    fn from(value: T) -> Self {
        Self(Some(value))
    }
}

/// LowCardinality wrapper for ClickHouse LowCardinality(T) type
///
/// In runtime it's transparent, but affects DDL generation
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct LowCardinality<T>(pub T);

impl<T: ClickHouseType> ClickHouseType for LowCardinality<T> {
    fn type_name() -> Cow<'static, str> {
        Cow::Owned(format!("LowCardinality({})", T::type_name()))
    }

    fn write_binary<W: Write>(&self, writer: &mut W) -> Result<(), WriteError> {
        // LowCardinality is handled by the server, we just write the inner value
        self.0.write_binary(writer)
    }

    fn read_binary<R: Read>(reader: &mut R) -> Result<Self, ReadError> {
        Ok(LowCardinality(T::read_binary(reader)?))
    }

    fn fixed_size() -> Option<usize> {
        T::fixed_size()
    }
}

impl<T> From<T> for LowCardinality<T> {
    fn from(value: T) -> Self {
        Self(value)
    }
}

/// Array type alias for ClickHouse Array(T)
pub type Array<T> = Vec<T>;

impl<T: ClickHouseType> ClickHouseType for Vec<T> {
    fn type_name() -> Cow<'static, str> {
        Cow::Owned(format!("Array({})", T::type_name()))
    }

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

        // Write each element
        for item in self {
            item.write_binary(writer)?;
        }
        Ok(())
    }

    fn read_binary<R: Read>(reader: &mut R) -> Result<Self, ReadError> {
        let len = super::primitives::read_leb128(reader)? as usize;
        let mut result = Vec::with_capacity(len);

        for _ in 0..len {
            result.push(T::read_binary(reader)?);
        }

        Ok(result)
    }
}

/// Map type alias for ClickHouse Map(K, V)
pub type Map<K, V> = HashMap<K, V>;

impl<K, V> ClickHouseType for HashMap<K, V>
where
    K: ClickHouseType + Eq + std::hash::Hash,
    V: ClickHouseType,
{
    fn type_name() -> Cow<'static, str> {
        Cow::Owned(format!("Map({}, {})", K::type_name(), V::type_name()))
    }

    fn write_binary<W: Write>(&self, writer: &mut W) -> Result<(), WriteError> {
        // Maps in RowBinary are serialized as Array(Tuple(K, V))
        // Format: Length (LEB128) | Key1 | Val1 | Key2 | Val2 ...
        super::primitives::write_leb128(writer, self.len() as u64)?;

        for (key, value) in self {
            key.write_binary(writer)?;
            value.write_binary(writer)?;
        }

        Ok(())
    }

    fn read_binary<R: Read>(reader: &mut R) -> Result<Self, ReadError> {
        let len = super::primitives::read_leb128(reader)? as usize;
        let mut result = HashMap::with_capacity(len);

        for _ in 0..len {
            let key = K::read_binary(reader)?;
            let value = V::read_binary(reader)?;
            result.insert(key, value);
        }

        Ok(result)
    }
}


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

    #[test]
    fn test_nullable_roundtrip() {
        let null: Nullable<i32> = Nullable::null();
        let mut buf = Vec::new();
        null.write_binary(&mut buf).unwrap();
        let decoded = Nullable::<i32>::read_binary(&mut buf.as_slice()).unwrap();
        assert!(decoded.is_null());

        let some = Nullable::some(42i32);
        let mut buf = Vec::new();
        some.write_binary(&mut buf).unwrap();
        let decoded = Nullable::<i32>::read_binary(&mut buf.as_slice()).unwrap();
        assert_eq!(decoded.0, Some(42));
    }

    #[test]
    fn test_array_roundtrip() {
        let arr = vec![1i32, 2, 3, 4, 5];
        let mut buf = Vec::new();
        arr.write_binary(&mut buf).unwrap();
        let decoded = Vec::<i32>::read_binary(&mut buf.as_slice()).unwrap();
        assert_eq!(arr, decoded);
    }
}
