use bytes::BytesMut;
use std::any::type_name;
use std::error::Error;
use std::sync::Arc;
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;
use super::EntityId;

#[derive(Clone)]
pub enum EntityRef<E: Entity> {
    Id(EntityId<E::EntityIdType>),
    Loaded(Arc<E>),
}

impl<E: Entity> EntityRef<E> {
    pub fn id(&self) -> &EntityId<E::EntityIdType> {
        return match self {
            EntityRef::Id(id) => id,
            EntityRef::Loaded(entity) => entity.id(),
        };
    }

    pub fn is_loaded(&self) -> bool {
        return match self {
            EntityRef::Id(_) => false,
            EntityRef::Loaded(_) => true,
        };
    }

    pub fn get(&self) -> &Arc<E> {
        return match self {
            EntityRef::Id(_) => panic!("Entity was not loaded"),
            EntityRef::Loaded(entity) => entity,
        };
    }

    pub fn into_entity(self) -> E {
        return match self {
            EntityRef::Id(_) => panic!("Entity was not loaded"),
            EntityRef::Loaded(entity) => Arc::unwrap_or_clone(entity),
        };
    }
}

impl<E: Entity> From<&EntityId<E::EntityIdType>> for EntityRef<E> {
    fn from(id: &EntityId<E::EntityIdType>) -> Self {
        return EntityRef::Id(id.clone());
    }
}

impl<E: Entity> From<E> for EntityRef<E> {
    fn from(entity: E) -> Self {
        return EntityRef::Loaded(Arc::new(entity));
    }
}

impl<E: Entity> From<&Arc<E>> for EntityRef<E> {
    fn from(entity: &Arc<E>) -> Self {
        return EntityRef::Loaded(entity.clone());
    }
}

pub trait EqId {
    fn eq_id(&self, other: &Self) -> bool;
}

impl<E: Entity> EqId for EntityRef<E> {
    fn eq_id(&self, other: &EntityRef<E>) -> bool {
        return self.id() == other.id();
    }
}

impl<E: EqId> EqId for Option<E> {
    fn eq_id(&self, other: &Option<E>) -> bool {
        return match (self, other) {
            (None, None) => true,
            (None, _) => false,
            (_, None) => false,
            (Some(s), Some(o)) => s.eq_id(o),
        };
    }
}

impl<E: Entity> std::fmt::Debug for EntityRef<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);

        let struct_name = format!("EntityRef<{type_name}>");

        let loaded = match self {
            EntityRef::Id(_) => false,
            EntityRef::Loaded(_) => true,
        };

        return f
            .debug_struct(&struct_name)
            .field("id", &self.id().as_ref())
            .field("loaded", &loaded)
            .finish();
    }
}

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

    fn accepts(ty: &Type) -> bool {
        return <EntityId<E::EntityIdType> as ToSql>::accepts(ty);
    }
}

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

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

    to_sql_checked!();
}
