use rust_decimal::Decimal;
use serde::{Serialize, Serializer, ser::SerializeMap, ser::SerializeSeq};
use serde_json::Value as JsonValue;

use crate::zbase::ZBASE32;

#[derive(Debug, Clone, PartialEq)]
pub enum DynValue {
    Void,
    Bool(bool),
    U8(u8),
    U16(u16),
    U32(u32),
    U64(u64),
    I32(i32),
    I64(i64),
    F32(f32),
    F64(f64),
    Str(String),
    Datetime(i64),
    Timestamp(u64),
    Decimal(Decimal),
    Id32(u32),
    Id64(u64),
    Fuid(u64),
    LowId(u32),
    Bytes(Vec<u8>),
    ArrayBytes(Vec<u8>),
    Array(Vec<DynValue>),
    Vec(Vec<DynValue>),
    Optional(Option<Box<DynValue>>),
    SimpleEnum {
        name: &'static str,
        variant: u8,
        variant_name: &'static str,
    },
    Struct {
        name: &'static str,
        fields: StructValue,
    },
    Enum {
        name: &'static str,
        variant: u8,
        variant_name: &'static str,
        field: Box<DynValue>,
    },
    RustJson(JsonValue),
    JsonBytes(String),
}

#[derive(Debug, Clone, PartialEq)]
pub enum StructValue {
    Named(Vec<(&'static str, DynValue)>),
    Unnamed(Vec<DynValue>),
}

fn ms_to_rfc3339<E: serde::ser::Error>(ms: i64) -> Result<String, E> {
    let t = time::OffsetDateTime::from_unix_timestamp_nanos((ms as i128) * 1_000_000)
        .map_err(serde::ser::Error::custom)?;
    t.format(&time::format_description::well_known::Rfc3339)
        .map_err(serde::ser::Error::custom)
}

impl Serialize for DynValue {
    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        use data_encoding::HEXLOWER;
        match self {
            DynValue::Void => s.serialize_unit(),
            DynValue::Bool(b) => s.serialize_bool(*b),
            DynValue::U8(v) => s.serialize_u8(*v),
            DynValue::U16(v) => s.serialize_u16(*v),
            DynValue::U32(v) => s.serialize_u32(*v),
            DynValue::U64(v) => s.serialize_u64(*v),
            DynValue::I32(v) => s.serialize_i32(*v),
            DynValue::I64(v) => s.serialize_i64(*v),
            DynValue::F32(v) => s.serialize_f32(*v),
            DynValue::F64(v) => s.serialize_f64(*v),
            DynValue::Str(v) => s.serialize_str(v),
            DynValue::Datetime(ms) => s.serialize_str(&ms_to_rfc3339::<S::Error>(*ms)?),
            DynValue::Timestamp(ms) => s.serialize_str(&ms_to_rfc3339::<S::Error>(*ms as i64)?),
            DynValue::Decimal(d) => s.serialize_str(&d.to_string()),
            DynValue::Id32(v) => s.serialize_str(&format!("{v:08x}")),
            DynValue::Id64(v) | DynValue::Fuid(v) => {
                s.serialize_str(&ZBASE32.encode(&v.to_be_bytes()))
            }
            DynValue::LowId(v) => s.serialize_u32(*v),
            DynValue::Bytes(v) | DynValue::ArrayBytes(v) => s.serialize_str(&HEXLOWER.encode(v)),
            DynValue::Array(items) | DynValue::Vec(items) => {
                let mut seq = s.serialize_seq(Some(items.len()))?;
                for item in items {
                    seq.serialize_element(item)?;
                }
                seq.end()
            }
            DynValue::Optional(None) => s.serialize_none(),
            DynValue::Optional(Some(v)) => v.serialize(s),
            DynValue::SimpleEnum { variant_name, .. } => s.serialize_str(variant_name),
            DynValue::Struct {
                fields: StructValue::Named(pairs),
                ..
            } => {
                let mut map = s.serialize_map(Some(pairs.len()))?;
                for (k, v) in pairs {
                    map.serialize_entry(k, v)?;
                }
                map.end()
            }
            DynValue::Struct {
                fields: StructValue::Unnamed(items),
                ..
            } => {
                let mut seq = s.serialize_seq(Some(items.len()))?;
                for item in items {
                    seq.serialize_element(item)?;
                }
                seq.end()
            }
            DynValue::Enum {
                variant_name,
                field,
                ..
            } => {
                let mut map = s.serialize_map(Some(1))?;
                map.serialize_entry(variant_name, field.as_ref())?;
                map.end()
            }
            DynValue::RustJson(v) => v.serialize(s),
            DynValue::JsonBytes(raw) => {
                let parsed: JsonValue =
                    serde_json::from_str(raw).map_err(serde::ser::Error::custom)?;
                parsed.serialize(s)
            }
        }
    }
}
