use std::fmt::Display;

use serde::{Deserialize, Serialize};

use crate::{
    ntds::Error,
    value::{FromValue, StringOrStringSet, ToStringOrStringSet},
    CDatabase,
};

#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize)]
pub struct ClassId(u32);

impl FromValue for ClassId {
    fn from_value_opt(value: &crate::cache::Value) -> crate::ntds::Result<Option<Self>>
    where
        Self: Sized,
    {
        match value {
            crate::cache::Value::Null(_) => Ok(None),
            crate::cache::Value::U8(n) => Ok(Some(Self((*n).into()))),
            crate::cache::Value::I16(n) => {
                Ok(Some(Self((*n).try_into().map_err(|_| {
                    Error::InvalidValueDetected(value.to_string(), "u32")
                })?)))
            }
            crate::cache::Value::I32(n) => {
                Ok(Some(Self((*n).try_into().map_err(|_| {
                    Error::InvalidValueDetected(value.to_string(), "u32")
                })?)))
            }
            crate::cache::Value::Currency(n) => {
                Ok(Some(Self((*n).try_into().map_err(|_| {
                    Error::InvalidValueDetected(value.to_string(), "u32")
                })?)))
            }
            crate::cache::Value::U32(n) => Ok(Some(Self(*n))),
            crate::cache::Value::I64(n) => {
                Ok(Some(Self((*n).try_into().map_err(|_| {
                    Error::InvalidValueDetected(value.to_string(), "u32")
                })?)))
            }
            crate::cache::Value::U16(n) => Ok(Some(Self((*n).into()))),
            _ => Err(Error::InvalidValueDetected(value.to_string(), "i32")),
        }
    }
}

impl Display for ClassId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl ToStringOrStringSet for ClassId {
    fn to_string_or_stringset(&self, database: &CDatabase<'_, '_>) -> StringOrStringSet {
        if let Some(ptr) = database
            .data_table()
            .data_table()
            .metadata()
            .object_classes()
            .get(self)
        {
            if let Some(cn) = database
                .record(ptr.ds_record_id())
                .and_then(|entry| entry.cn().as_ref())
            {
                return StringOrStringSet::String(cn.to_string());
            }
        }
        StringOrStringSet::String(self.0.to_string())
    }
}
