use bytes::Bytes;
use flexbuffers::{MapBuilder, VectorBuilder};
use heck::ToPascalCase;
use serde::{Deserialize, Serialize};
use serde_json::Value;

pub enum ActionOp {
    Set(u8, Bytes),
    Call(u8, Bytes),
}

pub enum ActionRes {
    Set,
    Call(Bytes),
}

/// Struct representation of a field in the `content.json`
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ContentField {
    /// field name
    pub name: String,
    /// JSON Value for the field. Type will
    /// be validated when uploaded.
    pub value: Value,
}

/// Struct representation of an object in the `content.json`
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ContentObject {
    /// specifies which content definition the object should
    /// be validated against.
    pub instance_of: String,
    /// fields to be validated and then stored for consumption
    /// by templates.
    pub fields: Vec<ContentField>,
}

/// Fields are used to describe properties on a model,
/// IO for actions/integrations and content definitions.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Field {
    /// The index used for compact, vector based serialization.
    /// The index is preserved even if the property name changes.
    /// Each field must be assigned an index value from 0-255, with
    /// no gaps (i.e cannot go from 2 to 4 without also having a field
    /// with an index of 3).
    pub idx: u8,
    /// this is the name that is used when accessing a property in a template
    /// and will be the name that is used in the JSON serialized format.
    ///
    /// ## Definition
    ///
    /// ```json
    /// {
    ///     "models": [
    ///         {
    ///             "name": "post",
    ///             "fields": [
    ///                 { "idx": 0, "name": "title", kind: "String" }
    ///             ]
    ///         }
    ///     ]
    /// }
    /// ```
    ///
    /// ## Template
    ///
    /// ```html
    /// <h1>{{ post.title }}</h1>
    /// ```
    ///
    /// ## JSON serialized results
    ///
    /// ```json
    /// {
    ///     "posts": [
    ///         { "title": "some title 1" },
    ///         { "title": "some title 2" },
    ///     ]
    /// }
    /// ```
    pub name: String,
    /// the "kind" represents the type of the value that will
    /// be stored or sent.
    pub kind: Kind,
    /// Used for models and content definitions. Determines whether
    /// you can access an object/item by this property. All items
    /// have are automatically stored ID that can be used for look
    /// up if no properties are indexed.
    ///
    /// Note: `indexed` fields also have a uniqueness constraint.
    /// A `searchable` property will be available in the future, for
    /// a non-unique index.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub indexed: Option<bool>,
    /// Used for integrations, so that a property on the external
    /// resource's response can be renamed.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub mapping: Option<String>,
}

/// Defines the types recognized by this framework.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Kind {
    /// null/void/undefined/None
    Void,
    /// boolean/true/false
    Bool,
    /// integers between
    I8,
    /// integers between
    U8,
    /// integers between
    I16,
    /// integers between
    U16,
    /// integers between
    I32,
    /// integers between
    U32,
    /// integers between
    I64,
    /// integers between
    U64,
    /// floats between
    F32,
    /// floats between
    F64,
    /// universal unique identifiers. 16 bytes
    /// or 36 characters.
    Uuid,
    /// array of bytes.
    Blob,
    /// text/string/characters.
    String,
    /// array of values.
    List {
        /// the type for the values stored in the list.
        kind: Box<Kind>,
    },
    // Enum { name: String, opts: Vec<String> },
    /// complex kind with its own name and subfields.
    Object {
        /// the object definition's name.
        name: String,
        /// nested subfields.
        fields: Vec<Field>,
    },
}

impl Kind {
    pub fn to_stringified_type(&self, prefix: &str) -> (String, Option<(String, Vec<Field>)>) {
        match self {
            Self::Void => ("()".to_string(), None),
            Self::Bool => ("bool".to_string(), None),
            Self::I8 => ("i8".to_string(), None),
            Self::U8 => ("u8".to_string(), None),
            Self::I16 => ("i16".to_string(), None),
            Self::U16 => ("u16".to_string(), None),
            Self::I32 => ("i32".to_string(), None),
            Self::U32 => ("u32".to_string(), None),
            Self::I64 => ("i64".to_string(), None),
            Self::U64 => ("u64".to_string(), None),
            Self::F32 => ("f32".to_string(), None),
            Self::F64 => ("f64".to_string(), None),
            Self::Uuid => ("[u8; 16]".to_string(), None),
            Self::Blob => ("Vec<u8>".to_string(), None),
            Self::String => ("String".to_string(), None),
            Self::List { kind } => {
                let (kind, fields) = kind.to_stringified_type(prefix);
                (format!("Vec<{}>", kind), fields)
            }
            // Self::Enum { name, opts } => {
            //     let enum_name = format!("{}{}", prefix, name.to_pascal_case());

            // }
            Self::Object { name, fields } => {
                let struct_name = format!("{}{}", prefix, name.to_pascal_case());
                (struct_name.clone(), Some((struct_name, fields.clone())))
            }
        }
    }

    pub fn to_stringified_type_w_lifetime(
        &self,
        prefix: &str,
        lifetime: &str,
    ) -> (String, Option<(String, Vec<Field>)>) {
        match self {
            Self::Void => ("()".to_string(), None),
            Self::Bool => ("bool".to_string(), None),
            Self::I8 => ("i8".to_string(), None),
            Self::U8 => ("u8".to_string(), None),
            Self::I16 => ("i16".to_string(), None),
            Self::U16 => ("u16".to_string(), None),
            Self::I32 => ("i32".to_string(), None),
            Self::U32 => ("u32".to_string(), None),
            Self::I64 => ("i64".to_string(), None),
            Self::U64 => ("u64".to_string(), None),
            Self::F32 => ("f32".to_string(), None),
            Self::F64 => ("f64".to_string(), None),
            Self::Uuid => ("[u8; 16]".to_string(), None),
            Self::Blob => (format!("&'{} [u8]", lifetime), None),
            Self::String => (format!("&'{} str", lifetime), None),
            Self::List { kind } => {
                let (kind, fields) = kind.to_stringified_type_w_lifetime(prefix, lifetime);
                (format!("Vec<{}>", kind), fields)
            }
            // Self::Enum { name, opts } => {
            //     let enum_name = format!("{}{}", prefix, name.to_pascal_case());

            // }
            Self::Object { name, fields } => {
                let struct_name = format!("{}{}", prefix, name.to_pascal_case());
                (struct_name.clone(), Some((struct_name, fields.clone())))
            }
        }
    }

    /// based on https://docs.rs/flexbuffers/latest/flexbuffers/struct.Reader.html
    pub fn to_flexbuffer_accessor(&self) -> Option<&'static str> {
        match self {
            Self::Void => None,
            Self::Bool => Some(".as_bool()"),
            Self::I8 => Some(".as_i8()"),
            Self::U8 => Some(".as_u8()"),
            Self::I16 => Some(".as_i16()"),
            Self::U16 => Some(".as_u16()"),
            Self::I32 => Some(".as_i32()"),
            Self::U32 => Some(".as_u32()"),
            Self::I64 => Some(".as_i64()"),
            Self::U64 => Some(".as_u64()"),
            Self::F32 => Some(".as_f32()"),
            Self::F64 => Some(".as_f64()"),
            Self::Uuid => Some(".as_blob()"),
            Self::Blob => Some(".as_blob()"),
            Self::String => Some(".as_str()"),
            Self::List { kind } => None,
            // Self::Enum { name, opts } => None,
            Self::Object { name, fields } => None,
        }
    }
}

/// Validates, converts and pushes a JSON Value to a FlexBuffer vector.
pub fn json_to_flexbuffer_vec(
    kind: &Kind,
    value: &serde_json::Value,
    vec: &mut VectorBuilder<'_>,
) -> Result<(), Box<dyn std::error::Error>> {
    match &value {
        serde_json::Value::Null => {
            if let Kind::Void = kind {
                vec.push(())
            } else {
                return Err("should not be null".into());
            }
        }
        serde_json::Value::Bool(v) => {
            if let Kind::Bool = kind {
                vec.push(*v);
            } else {
                return Err("got back invalid type for...".into());
            }
        }
        serde_json::Value::Number(v) => match kind {
            Kind::I8 => {
                if let Some(number) = v.as_i64() {
                    if number <= i8::MAX.into() && number >= i8::MIN.into() {
                        vec.push(number as i8);
                    }
                } else {
                    return Err("not a i8".into());
                }
            }
            Kind::U8 => {
                if let Some(number) = v.as_u64() {
                    if number <= u8::MAX.into() {
                        vec.push(number as u8);
                    }
                } else {
                    return Err("not a u8".into());
                }
            }
            Kind::I16 => {
                if let Some(number) = v.as_i64() {
                    if number <= i16::MAX.into() && number >= i16::MIN.into() {
                        vec.push(number as i16);
                    }
                } else {
                    return Err("not a i16".into());
                }
            }
            Kind::U16 => {
                if let Some(number) = v.as_u64() {
                    if number <= u16::MAX.into() {
                        vec.push(number as u16);
                    }
                } else {
                    return Err("not a u16".into());
                }
            }
            Kind::I32 => {
                if let Some(number) = v.as_i64() {
                    if number <= i32::MAX.into() && number >= i32::MIN.into() {
                        vec.push(number as i32);
                    }
                } else {
                    return Err("not a i32".into());
                }
            }
            Kind::U32 => {
                if let Some(number) = v.as_u64() {
                    if number <= u32::MAX.into() {
                        vec.push(number as u32);
                    }
                } else {
                    return Err("not a u32".into());
                }
            }
            Kind::I64 => {
                if let Some(number) = v.as_i64() {
                    vec.push(number);
                } else {
                    return Err("not a i64".into());
                }
            }
            Kind::U64 => {
                if let Some(number) = v.as_u64() {
                    vec.push(number);
                } else {
                    return Err("not a u64".into());
                }
            }
            Kind::F32 => {
                if let Some(number) = v.as_f64() {
                    if number <= f32::MAX.into() && number >= f32::MIN.into() {
                        vec.push(number as f32);
                    }
                } else {
                    return Err("not a f32".into());
                }
            }
            Kind::F64 => {
                if let Some(number) = v.as_f64() {
                    vec.push(number);
                } else {
                    return Err("not a f64".into());
                }
            }
            _ => return Err("not supposed to be a number".into()),
        },
        serde_json::Value::String(v) => match kind {
            Kind::String => {
                vec.push(v.as_str());
            }
            Kind::Uuid => {}
            _ => return Err("not string type".into()),
        },
        serde_json::Value::Array(v) => match kind {
            Kind::List { kind } => {
                let mut vec = vec.start_vector();

                for value in v {
                    json_to_flexbuffer_vec(kind, value, &mut vec)?;
                }

                vec.end_vector();
            }
            Kind::Blob => {}
            Kind::Uuid => {}
            _ => return Err("not array type".into()),
        },
        serde_json::Value::Object(v) => {
            if let Kind::Object { name: _, fields } = &kind {
                // todo: don't use maps, just use indexes on fields
                let mut map = vec.start_map();

                for field in fields {
                    let name = match &field.mapping {
                        Some(mapping) => mapping,
                        None => &field.name,
                    };

                    json_to_flexbuffer_map(field, &v[&field.name], &mut map, name)?;
                }

                map.end_map();
            }
        }
    }

    Ok(())
}

// todo: don't use maps, just use indexes on fields to push to a vec
/// Validates, converts and inserts a JSON Value into a FlexBuffer map.
pub fn json_to_flexbuffer_map(
    field: &Field,
    value: &serde_json::Value,
    map: &mut MapBuilder<'_>,
    name: &String,
) -> Result<(), Box<dyn std::error::Error>> {
    match &value {
        serde_json::Value::Null => {
            if let Kind::Void = field.kind {
                map.push(name, ());
            } else {
                return Err("should not be null".into());
            }
        }
        serde_json::Value::Bool(v) => {
            if let Kind::Bool = field.kind {
                map.push(name, *v);
            } else {
                return Err("got back invalid type for...".into());
            }
        }
        serde_json::Value::Number(v) => match field.kind {
            Kind::I8 => {
                if let Some(number) = v.as_i64() {
                    if number <= i8::MAX.into() && number >= i8::MIN.into() {
                        map.push(name, number as i8);
                    }
                } else {
                    return Err("not a i8".into());
                }
            }
            Kind::U8 => {
                if let Some(number) = v.as_u64() {
                    if number <= u8::MAX.into() {
                        map.push(name, number as u8);
                    }
                } else {
                    return Err("not a u8".into());
                }
            }
            Kind::I16 => {
                if let Some(number) = v.as_i64() {
                    if number <= i16::MAX.into() && number >= i16::MIN.into() {
                        map.push(name, number as i16);
                    }
                } else {
                    return Err("not a i16".into());
                }
            }
            Kind::U16 => {
                if let Some(number) = v.as_u64() {
                    if number <= u16::MAX.into() {
                        map.push(name, number as u16);
                    }
                } else {
                    return Err("not a u16".into());
                }
            }
            Kind::I32 => {
                if let Some(number) = v.as_i64() {
                    if number <= i32::MAX.into() && number >= i32::MIN.into() {
                        map.push(name, number as i32);
                    }
                } else {
                    return Err("not a i32".into());
                }
            }
            Kind::U32 => {
                if let Some(number) = v.as_u64() {
                    if number <= u32::MAX.into() {
                        map.push(name, number as u32);
                    }
                } else {
                    return Err("not a u32".into());
                }
            }
            Kind::I64 => {
                if let Some(number) = v.as_i64() {
                    map.push(name, number);
                } else {
                    return Err("not a i64".into());
                }
            }
            Kind::U64 => {
                if let Some(number) = v.as_u64() {
                    map.push(name, number);
                } else {
                    return Err("not a u64".into());
                }
            }
            Kind::F32 => {
                if let Some(number) = v.as_f64() {
                    if number <= f32::MAX.into() && number >= f32::MIN.into() {
                        map.push(name, number as f32);
                    }
                } else {
                    return Err("not a f32".into());
                }
            }
            Kind::F64 => {
                if let Some(number) = v.as_f64() {
                    map.push(name, number);
                } else {
                    return Err("not a f64".into());
                }
            }
            _ => return Err("not supposed to be a number".into()),
        },
        serde_json::Value::String(v) => match field.kind {
            Kind::String => {
                map.push(name, v.as_str());
            }
            Kind::Uuid => {}
            _ => return Err("not string type".into()),
        },
        serde_json::Value::Array(v) => match &field.kind {
            Kind::List { kind } => {
                let mut vec = map.start_vector(name);

                for value in v {
                    json_to_flexbuffer_vec(&kind, value, &mut vec)?;
                }

                vec.end_vector();
            }
            Kind::Blob => {}
            Kind::Uuid => {}
            _ => return Err("not array type".into()),
        },
        serde_json::Value::Object(v) => {
            if let Kind::Object { name: _, fields } = &field.kind {
                let mut map = map.start_map(name);

                for field in fields {
                    let name = match &field.mapping {
                        Some(mapping) => mapping,
                        None => &field.name,
                    };

                    json_to_flexbuffer_map(field, &v[&field.name], &mut map, name)?;
                }

                map.end_map();
            }
        }
    }

    Ok(())
}
