use bytes::BytesMut;
use std::any::type_name;
use std::error::Error;
use std::marker::PhantomData;
use tokio_postgres::types::to_sql_checked;
use tokio_postgres::types::FromSql;
use tokio_postgres::types::IsNull;
use tokio_postgres::types::ToSql;
use tokio_postgres::types::Type;

use super::Entity;

// We only support string IDs to simplify things

#[derive(Clone)]
pub struct EntityId<E: Entity>(String, PhantomData<E>);

impl<E: Entity> EntityId<E> {
    pub const fn new(s: String) -> Self {
        return Self(s, PhantomData);
    }

    pub const fn as_ref(&self) -> &String {
        return &self.0;
    }

    pub fn into_string(self) -> String {
        return self.0;
    }
}

impl<E: Entity> PartialEq for EntityId<E> {
    fn eq(&self, other: &Self) -> bool {
        return self.0 == other.0;
    }
}

// #[derive(Eq)] doesn't seem to work well with PhantomData sometimes (?)
impl<E: Entity> Eq for EntityId<E> {}

// #[derive(Hash)] doesn't seem to work well with PhantomData sometimes (?)
impl<E: Entity> std::hash::Hash for EntityId<E> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.0.hash(state);
    }
}

impl<E: Entity> std::fmt::Debug for EntityId<E> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let type_name = type_name::<E>();
        let type_name = type_name.rsplit_once("::").map_or(type_name, |pair| pair.1);

        return write!(f, "EntityId<{}>(\"{}\")", type_name, self.0);
    }
}

impl<E: Entity> From<&str> for EntityId<E> {
    fn from(s: &str) -> Self {
        return Self::new(s.to_string());
    }
}

impl<E: Entity> From<String> for EntityId<E> {
    fn from(s: String) -> Self {
        return Self::new(s);
    }
}

impl<'a, E: Entity> FromSql<'a> for EntityId<E> {
    fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Send + Sync>> {
        return <String as FromSql<'a>>::from_sql(ty, raw).map(|s| EntityId::new(s));
    }

    fn accepts(ty: &Type) -> bool {
        return <String as FromSql<'a>>::accepts(ty);
    }
}

impl<E: Entity> ToSql for EntityId<E> {
    fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Send + Sync>>
    where
        Self: Sized,
    {
        return self.as_ref().to_sql(ty, out);
    }

    fn accepts(ty: &Type) -> bool
    where
        Self: Sized,
    {
        return <String as ToSql>::accepts(ty);
    }

    to_sql_checked!();
}

impl<E: Entity> serde::Serialize for EntityId<E> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        return serializer.serialize_str(self.as_ref());
    }
}

impl<'de, E: Entity> serde::Deserialize<'de> for EntityId<E> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        return Ok(EntityId::new(String::deserialize(deserializer)?));
    }
}

pub trait EntityIdLike<'a, E: Entity>: 'a + ToSql + Send + Sync {
    fn as_str(&self) -> &'a str;
}

impl<'a, E: Entity> EntityIdLike<'a, E> for &'a EntityId<E> {
    fn as_str(&self) -> &'a str {
        return self.as_ref();
    }
}

impl<'a, E: Entity> EntityIdLike<'a, E> for &'a str {
    fn as_str(&self) -> &'a str {
        return self;
    }
}
