use crate::DbError;
use crate::DbErrorType;
use std::net::IpAddr;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::time::Duration;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;

pub trait Serialize: Sized {
    fn serialize(&self) -> Vec<u8>;
    fn deserialize(bytes: &[u8]) -> Result<Self, DbError>;
    fn serialized_size(&self) -> u64;
}

pub trait SerializeStatic: Serialize {
    fn serialized_size_static() -> u64 {
        std::mem::size_of::<Self>() as u64
    }
}

impl SerializeStatic for i64 {}
impl SerializeStatic for u64 {}
impl SerializeStatic for f64 {}

impl Serialize for i64 {
    fn serialize(&self) -> Vec<u8> {
        self.to_le_bytes().to_vec()
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        Ok(Self::from_le_bytes(
            bytes
                .get(0..std::mem::size_of::<Self>())
                .ok_or_else(|| {
                    DbError::serialization(DbErrorType::OutOfBounds, "i64 deserialization error")
                })?
                .try_into()?,
        ))
    }

    fn serialized_size(&self) -> u64 {
        Self::serialized_size_static()
    }
}

impl Serialize for u64 {
    fn serialize(&self) -> Vec<u8> {
        self.to_le_bytes().to_vec()
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        Ok(Self::from_le_bytes(
            bytes
                .get(0..std::mem::size_of::<Self>())
                .ok_or_else(|| {
                    DbError::serialization(DbErrorType::OutOfBounds, "u64 deserialization error")
                })?
                .try_into()?,
        ))
    }

    fn serialized_size(&self) -> u64 {
        Self::serialized_size_static()
    }
}

impl Serialize for f64 {
    fn serialize(&self) -> Vec<u8> {
        self.to_le_bytes().to_vec()
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        Ok(Self::from_le_bytes(
            bytes
                .get(0..std::mem::size_of::<Self>())
                .ok_or_else(|| {
                    DbError::serialization(DbErrorType::OutOfBounds, "f64 deserialization error")
                })?
                .try_into()?,
        ))
    }

    fn serialized_size(&self) -> u64 {
        Self::serialized_size_static()
    }
}

impl Serialize for usize {
    fn serialize(&self) -> Vec<u8> {
        (*self as u64).serialize()
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        let value = u64::deserialize(bytes)?;
        Ok(usize::try_from(value)?)
    }

    fn serialized_size(&self) -> u64 {
        u64::serialized_size_static()
    }
}

impl Serialize for String {
    fn serialize(&self) -> Vec<u8> {
        let mut bytes = Vec::with_capacity(self.serialized_size() as usize);
        bytes.extend(self.len().serialize());
        bytes.extend(self.as_bytes());

        bytes
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        let len = usize::deserialize(bytes)?;
        let begin = len.serialized_size() as usize;
        let end = begin + len;

        Ok(String::from_utf8(
            bytes
                .get(begin..end)
                .ok_or_else(|| {
                    DbError::serialization(DbErrorType::OutOfBounds, "String deserialization error")
                })?
                .to_vec(),
        )?)
    }

    fn serialized_size(&self) -> u64 {
        self.len().serialized_size() + self.len() as u64
    }
}

impl Serialize for bool {
    fn serialize(&self) -> Vec<u8> {
        vec![*self as u8]
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        bytes
            .first()
            .ok_or(DbError::serialization(
                DbErrorType::OutOfBounds,
                "bool deserialization error",
            ))
            .map(|&b| b != 0)
    }

    fn serialized_size(&self) -> u64 {
        1
    }
}

impl<T: Serialize> Serialize for Vec<T> {
    fn serialize(&self) -> Vec<u8> {
        let mut bytes = Vec::with_capacity(self.serialized_size() as usize);
        bytes.extend(self.len().serialize());

        for value in self {
            bytes.extend(value.serialize());
        }

        bytes
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        let len = usize::deserialize(bytes)?;
        let mut begin = len.serialized_size() as usize;
        let mut vec = Self::with_capacity(len);

        for _ in 0..len {
            let value = T::deserialize(&bytes[begin..]).map_err(|_| {
                DbError::serialization(
                    DbErrorType::OutOfBounds,
                    format!("Vec<{}> deserialization error", std::any::type_name::<T>()),
                )
            })?;
            begin += value.serialized_size() as usize;
            vec.push(value);
        }

        Ok(vec)
    }

    fn serialized_size(&self) -> u64 {
        let mut len = self.len().serialized_size();

        for value in self {
            len += value.serialized_size();
        }

        len
    }
}

impl Serialize for Vec<u8> {
    fn serialize(&self) -> Vec<u8> {
        let mut bytes = Vec::with_capacity(self.serialized_size() as usize);
        bytes.extend(self.len().serialize());
        bytes.extend(self);

        bytes
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        let len = usize::deserialize(bytes)?;
        let begin = len.serialized_size() as usize;
        let end = begin + len;

        Ok(bytes
            .get(begin..end)
            .ok_or_else(|| {
                DbError::serialization(DbErrorType::OutOfBounds, "Vec<u8> deserialization error")
            })?
            .to_vec())
    }

    fn serialized_size(&self) -> u64 {
        self.len().serialized_size() + self.len() as u64
    }
}

impl Serialize for PathBuf {
    fn serialize(&self) -> Vec<u8> {
        self.to_string_lossy().to_string().serialize()
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        let s = String::deserialize(bytes)?;
        Ok(PathBuf::from(s))
    }

    fn serialized_size(&self) -> u64 {
        self.to_string_lossy().to_string().serialized_size()
    }
}

impl Serialize for SystemTime {
    fn serialize(&self) -> Vec<u8> {
        let (duration, before_epoch) = match self.duration_since(UNIX_EPOCH) {
            Ok(duration) => (duration, false),
            Err(duration) => (duration.duration(), true),
        };
        let secs = duration.as_secs();
        let nanos = duration.subsec_nanos();
        let mut bytes = [0_u8; 13];
        bytes[0..8].copy_from_slice(&secs.to_le_bytes());
        bytes[8..12].copy_from_slice(&nanos.to_le_bytes());
        bytes[12] = if before_epoch { 0_u8 } else { 1_u8 };
        bytes.to_vec()
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        if bytes.len() < 13 {
            return Err(DbError::serialization(
                DbErrorType::NotEnoughData,
                format!(
                    "SystemTime deserialization error: got {} bytes, expected at least 13 bytes",
                    bytes.len()
                ),
            ));
        }
        let mut secs_bytes = [0_u8; 8];
        secs_bytes.copy_from_slice(&bytes[0..8]);
        let mut nanos_bytes = [0_u8; 4];
        nanos_bytes.copy_from_slice(&bytes[8..12]);
        let before_epoch = bytes[12] == 0_u8;
        let secs = u64::from_le_bytes(secs_bytes);
        let nanos = u32::from_le_bytes(nanos_bytes);
        let duration = Duration::new(secs, nanos);

        if before_epoch {
            Ok(UNIX_EPOCH.checked_sub(duration).ok_or_else(|| {
                DbError::serialization(
                    DbErrorType::OutOfBounds,
                    "SystemTime before UNIX_EPOCH is too far in the past",
                )
            })?)
        } else {
            Ok(UNIX_EPOCH.checked_add(duration).ok_or_else(|| {
                DbError::serialization(
                    DbErrorType::OutOfBounds,
                    "SystemTime after UNIX_EPOCH is too far in the future",
                )
            })?)
        }
    }

    fn serialized_size(&self) -> u64 {
        13
    }
}

impl Serialize for SocketAddr {
    fn serialize(&self) -> Vec<u8> {
        self.to_string().serialize()
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        let s = String::deserialize(bytes)?;
        s.parse().map_err(|e| {
            DbError::serialization(
                DbErrorType::TypeError,
                format!("Cannot convert string to SocketAddr: {e}"),
            )
        })
    }

    fn serialized_size(&self) -> u64 {
        self.to_string().serialized_size()
    }
}

impl Serialize for IpAddr {
    fn serialize(&self) -> Vec<u8> {
        self.to_string().serialize()
    }

    fn deserialize(bytes: &[u8]) -> Result<Self, DbError> {
        let s = String::deserialize(bytes)?;
        s.parse().map_err(|e| {
            DbError::serialization(
                DbErrorType::TypeError,
                format!("Cannot convert string to IpAddr: {e}"),
            )
        })
    }

    fn serialized_size(&self) -> u64 {
        self.to_string().serialized_size()
    }
}

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

    #[test]
    fn i64() {
        let original = -10_i64;
        let serialized_size = original.serialized_size();
        let mut bytes = original.serialize();

        assert_eq!(bytes.len() as u64, serialized_size);

        bytes.push(0);
        let deserialized = i64::deserialize(&bytes).unwrap();

        assert_eq!(original, deserialized);
    }

    #[test]
    fn i64_out_of_bounds() {
        assert_eq!(
            i64::deserialize(&Vec::<u8>::new()),
            Err(DbError::serialization(
                DbErrorType::OutOfBounds,
                "i64 deserialization error"
            ))
        );
    }

    #[test]
    fn u64() {
        let original = 10_u64;
        let serialized_size = original.serialized_size();
        let mut bytes = original.serialize();

        assert_eq!(bytes.len() as u64, serialized_size);

        bytes.push(0);
        let deserialized = u64::deserialize(&bytes).unwrap();

        assert_eq!(original, deserialized);
    }

    #[test]
    fn u64_out_of_bounds() {
        assert_eq!(
            u64::deserialize(&Vec::<u8>::new()),
            Err(DbError::serialization(
                DbErrorType::OutOfBounds,
                "u64 deserialization error"
            ))
        );
    }

    #[test]
    fn f64() {
        use std::f64::consts::PI;

        let original = -PI;
        let serialized_size = original.serialized_size();
        let mut bytes = original.serialize();

        assert_eq!(bytes.len() as u64, serialized_size);

        bytes.push(0);
        let deserialized = f64::deserialize(&bytes).unwrap();

        assert_eq!(original, deserialized);
    }

    #[test]
    fn f64_out_of_bounds() {
        assert_eq!(
            f64::deserialize(&Vec::<u8>::new()),
            Err(DbError::serialization(
                DbErrorType::OutOfBounds,
                "f64 deserialization error"
            ))
        );
    }

    #[test]
    fn usize() {
        let original: usize = 10;
        let serialized_size = original.serialized_size();
        let mut bytes = original.serialize();

        assert_eq!(bytes.len() as u64, serialized_size);

        bytes.push(0);
        let deserialized = usize::deserialize(&bytes).unwrap();

        assert_eq!(original, deserialized);
    }

    #[test]
    fn string() {
        let original = "This string has 24 bytes".to_string();
        let serialized_size = original.serialized_size();
        let mut bytes = original.serialize();

        assert_eq!(bytes.len() as u64, serialized_size);

        bytes.push(0);
        let deserialized = String::deserialize(&bytes).unwrap();

        assert_eq!(original, deserialized);
    }

    #[test]
    fn string_invalid_utf8() {
        let len: usize = 2;
        let mut bytes = len.serialize();
        bytes.push(0xdf);
        bytes.push(0xff);

        assert!(String::deserialize(&bytes).is_err());
    }

    #[test]
    fn string_out_of_bounds() {
        let mut bytes = "This string has 24 bytes".to_string().serialize();
        bytes.pop();

        assert_eq!(
            String::deserialize(&bytes),
            Err(DbError::serialization(
                DbErrorType::OutOfBounds,
                "String deserialization error"
            ))
        );
    }

    #[test]
    fn ar_u8() {
        let original = vec![1_u8, 2_u8, 3_u8];
        let serialized_size = original.serialized_size();
        let mut bytes = original.serialize();

        assert_eq!(bytes.len() as u64, serialized_size);

        bytes.push(0);
        let deserialized = Vec::<u8>::deserialize(&bytes).unwrap();

        assert_eq!(original, deserialized);
    }

    #[test]
    fn ar_u8_out_of_bounds() {
        let mut bytes = vec![1_u8, 2_u8, 3_u8].serialize();
        bytes.pop();

        assert_eq!(
            Vec::<u8>::deserialize(&bytes),
            Err(DbError::serialization(
                DbErrorType::OutOfBounds,
                "Vec<u8> deserialization error"
            ))
        );
    }

    #[test]
    fn vec_u64() {
        let original = vec![1_u64, 2_u64, 3_u64];
        let serialized_size = original.serialized_size();
        let mut bytes = original.serialize();

        assert_eq!(bytes.len() as u64, serialized_size);

        bytes.push(0);
        let deserialized = Vec::<u64>::deserialize(&bytes).unwrap();

        assert_eq!(original, deserialized);
    }

    #[test]
    fn vec_u64_out_of_bounds() {
        let mut bytes = vec![1_u64, 2_u64, 3_u64].serialize();
        bytes.pop();

        assert_eq!(
            Vec::<u64>::deserialize(&bytes),
            Err(DbError::serialization(
                DbErrorType::OutOfBounds,
                "Vec<u64> deserialization error"
            ))
        );
    }

    #[test]
    fn vec_string() {
        let original = vec!["Hello".to_string(), "World".to_string()];
        let serialized_size = original.serialized_size();
        let mut bytes = original.serialize();

        assert_eq!(bytes.len() as u64, serialized_size);
        bytes.push(0);
        let deserialized = Vec::<String>::deserialize(&bytes).unwrap();

        assert_eq!(original, deserialized);
    }

    #[test]
    fn vec_string_out_of_bounds() {
        let mut bytes = vec!["Hello".to_string(), "World".to_string()].serialize();
        bytes.pop();

        assert_eq!(
            Vec::<String>::deserialize(&bytes),
            Err(DbError::serialization(
                DbErrorType::OutOfBounds,
                format!(
                    "Vec<{}> deserialization error",
                    std::any::type_name::<String>()
                )
            ))
        );

        let len: usize = 1;
        bytes = len.serialize();

        assert_eq!(
            Vec::<String>::deserialize(&bytes),
            Err(DbError::serialization(
                DbErrorType::OutOfBounds,
                format!(
                    "Vec<{}> deserialization error",
                    std::any::type_name::<String>()
                )
            ))
        );
    }

    #[test]
    fn path_buf() {
        let original = PathBuf::from("/some/test/path");
        let serialized_size = original.serialized_size();
        let bytes = original.serialize();

        assert_eq!(bytes.len() as u64, serialized_size);

        let deserialized = PathBuf::deserialize(&bytes).unwrap();

        assert_eq!(original, deserialized);
    }

    #[test]
    fn system_time() {
        let original = SystemTime::UNIX_EPOCH + Duration::from_secs(1_000_000);
        let serialized_size = original.serialized_size();
        let bytes = original.serialize();

        assert_eq!(bytes.len() as u64, serialized_size);

        let deserialized = SystemTime::deserialize(&bytes).unwrap();

        assert_eq!(original, deserialized);
    }

    #[test]
    fn socket_address() {
        let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
        let bytes = addr.serialize();
        let deserialized = SocketAddr::deserialize(&bytes).unwrap();

        assert_eq!(addr, deserialized);
    }

    #[test]
    fn ip_address() {
        let addr: IpAddr = "127.0.0.1".parse().unwrap();
        let bytes = addr.serialize();
        let deserialized = IpAddr::deserialize(&bytes).unwrap();

        assert_eq!(addr, deserialized);
    }
}
