use std::fmt::{Display, Formatter};

use itertools::Itertools;
use strum::IntoStaticStr;

use crate::cache::ColumnIndex;

#[derive(PartialEq, Debug, IntoStaticStr)]
pub enum Value {
    Null(()),
    Bool(bool),
    U8(u8),
    I16(i16),
    I32(i32),
    Currency(i64),
    F32(f32),
    F64(f64),
    DateTime(u64),
    Binary(Box<Vec<u8>>),
    Text(Box<String>),
    U32(u32),
    I64(i64),
    Guid(Box<Vec<u8>>),
    U16(u16),
    Multi(Vec<Self>),
}

impl Value {
    pub fn type_name(&self) -> &'static str {
        self.into()
    }

    pub fn from_record_opt(
        record: &libesedb::Record,
        column_index: &ColumnIndex,
    ) -> crate::ntds::Result<Option<Self>> {
        if record.is_long(**column_index)? {
            assert!(!record.is_multi(**column_index)?);
            let v = record.long(**column_index)?;
            let value = match v.variant() {
                libesedb::Value::Null(_) => Value::Null(()),
                libesedb::Value::Bool(_) => panic!("A Bool should not be stored as Long"),
                libesedb::Value::U8(_) => panic!("A U8 should not be stored as Long"),
                libesedb::Value::I16(_) => panic!("An I16 should not be stored as Long"),
                libesedb::Value::I32(_) => panic!("An I32 should not be stored as Long"),
                libesedb::Value::Currency(_) => panic!("A Currency should not be stored as Long"),
                libesedb::Value::F32(_) => panic!("A F32 should not be stored as Long"),
                libesedb::Value::F64(_) => panic!("A F64 should not be stored as Long"),
                libesedb::Value::DateTime(_) => panic!("A DateTime should not be stored as Long"),
                libesedb::Value::Binary(_) => Value::Binary(Box::new(v.vec()?)),
                libesedb::Value::Text(_) => Value::Text(Box::new(v.utf8()?)),
                libesedb::Value::LargeBinary(_) => Value::Binary(Box::new(v.vec()?)),
                libesedb::Value::LargeText(_) => Value::Text(Box::new(v.utf8()?)),
                libesedb::Value::SuperLarge(_) => Value::Binary(Box::new(v.vec()?)),
                libesedb::Value::U32(_) => panic!("A U32 should not be stored as Long"),
                libesedb::Value::I64(_) => panic!("An I64 should not be stored as Long"),
                libesedb::Value::Guid(_) => Value::Guid(Box::new(v.vec()?)),
                libesedb::Value::U16(_) => panic!("A U16 should not be stored as Long"),
                libesedb::Value::Long => panic!("A Long inside of a Long? This should not happen!"),
                libesedb::Value::Multi => {
                    panic!("A Multi inside of a Long? This should not happen!")
                }
            };
            Ok(Some(value))
        } else if record.is_multi(**column_index)? {
            assert!(!record.is_long(**column_index)?);
            Ok(Some(Value::Multi(
                record
                    .multi(**column_index)?
                    .iter_values()?
                    .map(|res| res.map_err(crate::ntds::Error::from))
                    .process_results(|i| i.map(Self::from).collect())?,
            )))
        } else {
            match record.value(**column_index)? {
                libesedb::Value::Null(_) => Ok(None),
                v => Ok(Some(v.into())),
            }
        }
    }

    pub fn multi_string(&self) -> Vec<String> {
        match self {
            Value::Null(_) => vec![],
            Value::Bool(v) => vec![v.to_string()],
            Value::U8(v) => vec![v.to_string()],
            Value::I16(v) => vec![v.to_string()],
            Value::I32(v) => vec![v.to_string()],
            Value::Currency(v) => vec![v.to_string()],
            Value::F32(v) => vec![v.to_string()],
            Value::F64(v) => vec![v.to_string()],
            Value::DateTime(v) => vec![v.to_string()],
            Value::Binary(v) => vec![format!("{v:?}")],
            Value::Text(v) => vec![v.to_string()],
            Value::U32(v) => vec![v.to_string()],
            Value::I64(v) => vec![v.to_string()],
            Value::Guid(v) => vec![format!("{v:?}")],
            Value::U16(v) => vec![v.to_string()],
            Value::Multi(values) => values.iter().map(|v| v.to_string()).collect(),
        }
    }
}

impl Eq for Value {}

impl From<libesedb::Value> for Value {
    fn from(value: libesedb::Value) -> Self {
        match value {
            libesedb::Value::Null(_) => Self::Null(()),
            libesedb::Value::Bool(v) => Self::Bool(v),
            libesedb::Value::U8(v) => Self::U8(v),
            libesedb::Value::I16(v) => Self::I16(v),
            libesedb::Value::I32(v) => Self::I32(v),
            libesedb::Value::Currency(v) => Self::Currency(v),
            libesedb::Value::F32(v) => Self::F32(v),
            libesedb::Value::F64(v) => Self::F64(v),
            libesedb::Value::DateTime(v) => Self::DateTime(v),
            libesedb::Value::Binary(v) => Self::Binary(Box::new(v)),
            libesedb::Value::Text(v) => Self::Text(Box::new(v)),
            libesedb::Value::LargeBinary(v) => Self::Binary(Box::new(v)),
            libesedb::Value::LargeText(v) => Self::Text(Box::new(v)),
            libesedb::Value::SuperLarge(v) => Self::Binary(Box::new(v)),
            libesedb::Value::U32(v) => Self::U32(v),
            libesedb::Value::I64(v) => Self::I64(v),
            libesedb::Value::Guid(v) => Self::Guid(Box::new(v)),
            libesedb::Value::U16(v) => Self::U16(v),
            v => unimplemented!("unable to convert {v:?}"),
        }
    }
}

impl Display for Value {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Value::Null(_) => write!(f, ""),
            Value::Bool(v) => write!(f, "{v}"),
            Value::U8(v) => write!(f, "{v}"),
            Value::I16(v) => write!(f, "{v}"),
            Value::I32(v) => write!(f, "{v}"),
            Value::Currency(v) => write!(f, "{v}"),
            Value::F32(v) => write!(f, "{v}"),
            Value::F64(v) => write!(f, "{v}"),
            Value::DateTime(v) => write!(f, "{v}"),
            Value::Binary(v) => write!(f, "{v:?}"),
            Value::Text(v) => write!(f, "{v}"),
            Value::U32(v) => write!(f, "{v}"),
            Value::I64(v) => write!(f, "{v}"),
            Value::Guid(v) => write!(f, "{v:?}"),
            Value::U16(v) => write!(f, "{v}"),
            Value::Multi(multi) => {
                let values: Vec<_> = multi.iter().map(|v| format!("{v}")).collect();
                write!(f, "[{}]", values.join(","))
            }
        }
    }
}
