//! The [`JsonValue`] enum and all its conversions to/from primitive and CRDT types.

use std::fmt::Display;

use indexmap::IndexMap;
use serde::{Deserialize, Serialize};

use crate::{keypair::AuthorId, list_crdt::ListCrdt, lww_crdt::LwwRegisterCrdt, op::PathSegment};

use super::{CrdtNode, CrdtNodeFromValue};

/// An enum representing a JSON value
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub enum JsonValue {
    #[default]
    Null,
    Bool(bool),
    Number(f64),
    String(String),
    Array(Vec<JsonValue>),
    Object(IndexMap<String, JsonValue>),
}

impl Display for JsonValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                JsonValue::Null => "null".to_string(),
                JsonValue::Bool(b) => b.to_string(),
                JsonValue::Number(n) => n.to_string(),
                JsonValue::String(s) => format!("\"{s}\""),
                JsonValue::Array(arr) => {
                    if arr.len() > 1 {
                        format!(
                            "[\n{}\n]",
                            arr.iter()
                                .map(|x| format!("  {x}"))
                                .collect::<Vec<_>>()
                                .join(",\n")
                        )
                    } else {
                        format!(
                            "[ {} ]",
                            arr.iter()
                                .map(|x| x.to_string())
                                .collect::<Vec<_>>()
                                .join(", ")
                        )
                    }
                }
                JsonValue::Object(obj) => format!(
                    "{{ {} }}",
                    obj.iter()
                        .map(|(k, v)| format!("  \"{k}\": {v}"))
                        .collect::<Vec<_>>()
                        .join(",\n")
                ),
            }
        )
    }
}

/// Allow easy conversion to and from serde's JSON format. This allows us to use the [`json!`]
/// macro
impl From<JsonValue> for serde_json::Value {
    fn from(value: JsonValue) -> Self {
        match value {
            JsonValue::Null => serde_json::Value::Null,
            JsonValue::Bool(x) => serde_json::Value::Bool(x),
            JsonValue::Number(x) => {
                serde_json::Value::Number(serde_json::Number::from_f64(x).unwrap())
            }
            JsonValue::String(x) => serde_json::Value::String(x),
            JsonValue::Array(x) => {
                serde_json::Value::Array(x.iter().map(|a| a.clone().into()).collect())
            }
            JsonValue::Object(x) => serde_json::Value::Object(
                x.iter()
                    .map(|(k, v)| (k.clone(), v.clone().into()))
                    .collect(),
            ),
        }
    }
}

impl From<serde_json::Value> for JsonValue {
    fn from(value: serde_json::Value) -> Self {
        match value {
            serde_json::Value::Null => JsonValue::Null,
            serde_json::Value::Bool(x) => JsonValue::Bool(x),
            serde_json::Value::Number(x) => JsonValue::Number(x.as_f64().unwrap()),
            serde_json::Value::String(x) => JsonValue::String(x),
            serde_json::Value::Array(x) => {
                JsonValue::Array(x.iter().map(|a| a.clone().into()).collect())
            }
            serde_json::Value::Object(x) => JsonValue::Object(
                x.iter()
                    .map(|(k, v)| (k.clone(), v.clone().into()))
                    .collect(),
            ),
        }
    }
}

impl JsonValue {
    pub fn into_json(self) -> serde_json::Value {
        self.into()
    }
}

/// Conversions from primitive types to [`JsonValue`]
impl From<bool> for JsonValue {
    fn from(val: bool) -> Self {
        JsonValue::Bool(val)
    }
}

impl From<i64> for JsonValue {
    fn from(val: i64) -> Self {
        JsonValue::Number(val as f64)
    }
}

impl From<i32> for JsonValue {
    fn from(val: i32) -> Self {
        JsonValue::Number(val as f64)
    }
}

impl From<f64> for JsonValue {
    fn from(val: f64) -> Self {
        JsonValue::Number(val)
    }
}

impl From<String> for JsonValue {
    fn from(val: String) -> Self {
        JsonValue::String(val)
    }
}

impl From<char> for JsonValue {
    fn from(val: char) -> Self {
        JsonValue::String(val.into())
    }
}

impl<T> From<Option<T>> for JsonValue
where
    T: CrdtNode,
{
    fn from(val: Option<T>) -> Self {
        match val {
            Some(x) => x.view(),
            None => JsonValue::Null,
        }
    }
}

impl<T> From<Vec<T>> for JsonValue
where
    T: CrdtNode,
{
    fn from(value: Vec<T>) -> Self {
        JsonValue::Array(value.iter().map(|x| x.view()).collect())
    }
}

/// Conversions from bool to CRDT
impl CrdtNodeFromValue for bool {
    fn node_from(value: JsonValue, _id: AuthorId, _path: Vec<PathSegment>) -> Result<Self, String> {
        if let JsonValue::Bool(x) = value {
            Ok(x)
        } else {
            Err(format!("failed to convert {value:?} -> bool"))
        }
    }
}

/// Conversions from f64 to CRDT
impl CrdtNodeFromValue for f64 {
    fn node_from(value: JsonValue, _id: AuthorId, _path: Vec<PathSegment>) -> Result<Self, String> {
        if let JsonValue::Number(x) = value {
            Ok(x)
        } else {
            Err(format!("failed to convert {value:?} -> f64"))
        }
    }
}

/// Conversions from i64 to CRDT
impl CrdtNodeFromValue for i64 {
    fn node_from(value: JsonValue, _id: AuthorId, _path: Vec<PathSegment>) -> Result<Self, String> {
        if let JsonValue::Number(x) = value {
            Ok(x as i64)
        } else {
            Err(format!("failed to convert {value:?} -> f64"))
        }
    }
}

/// Conversions from String to CRDT
impl CrdtNodeFromValue for String {
    fn node_from(value: JsonValue, _id: AuthorId, _path: Vec<PathSegment>) -> Result<Self, String> {
        if let JsonValue::String(x) = value {
            Ok(x)
        } else {
            Err(format!("failed to convert {value:?} -> String"))
        }
    }
}

/// Conversions from char to CRDT
impl CrdtNodeFromValue for char {
    fn node_from(value: JsonValue, _id: AuthorId, _path: Vec<PathSegment>) -> Result<Self, String> {
        if let JsonValue::String(x) = value.clone() {
            x.chars().next().ok_or(format!(
                "failed to convert {value:?} -> char: found a zero-length string"
            ))
        } else {
            Err(format!("failed to convert {value:?} -> char"))
        }
    }
}

impl<T> CrdtNodeFromValue for LwwRegisterCrdt<T>
where
    T: CrdtNode,
{
    fn node_from(value: JsonValue, id: AuthorId, path: Vec<PathSegment>) -> Result<Self, String> {
        let mut crdt = LwwRegisterCrdt::new(id, path);
        crdt.set(value);
        Ok(crdt)
    }
}

impl<T> CrdtNodeFromValue for ListCrdt<T>
where
    T: CrdtNode,
{
    fn node_from(value: JsonValue, id: AuthorId, path: Vec<PathSegment>) -> Result<Self, String> {
        if let JsonValue::Array(arr) = value {
            let mut crdt = ListCrdt::new(id, path);
            let result: Result<(), String> =
                arr.into_iter().enumerate().try_for_each(|(i, val)| {
                    crdt.insert_idx(i, val);
                    Ok(())
                });
            result?;
            Ok(crdt)
        } else {
            Err(format!("failed to convert {value:?} -> ListCRDT<T>"))
        }
    }
}
