//! Guest-side Value type for plugin data exchange.
//!
//! [`GuestValue`] mirrors the host's `Value` enum but uses owned types
//! (`String` instead of `Arc<str>`, `BTreeMap<String, _>` instead of
//! `BTreeMap<Arc<str>, _>`) so the guest crate has no dependency on
//! `kailash-value`. JSON serialization/deserialization handles the
//! host<->guest boundary transparently.

use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

/// Guest-side value type mirroring the host's `Value` enum.
///
/// Uses `#[serde(untagged)]` so the JSON wire format is identical to
/// the host's `Value` representation.
///
/// # Variant ordering
///
/// Serde `untagged` tries each variant in declaration order, so order
/// matters for correct deserialization:
/// - `Null` first (only matches JSON `null`)
/// - `Bool` before numbers (JSON `true`/`false`)
/// - `Integer` before `Float` (JSON integers should stay integral)
/// - `String` after numbers (JSON strings)
/// - `Array` and `Object` last
///
/// `Bytes` is intentionally excluded from the untagged enum because
/// raw binary cannot be represented directly in JSON; byte arrays are
/// base64-encoded as strings at the JSON level via a serde wrapper.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GuestValue {
    /// JSON `null`.
    Null,
    /// JSON boolean.
    Bool(bool),
    /// JSON integer (fits in `i64`).
    Integer(i64),
    /// JSON floating-point number.
    Float(f64),
    /// JSON string.
    String(String),
    /// Ordered array of values.
    Array(Vec<GuestValue>),
    /// Ordered map of string keys to values.
    Object(BTreeMap<String, GuestValue>),
}

/// A map of string keys to guest values, used as the primary I/O type
/// for plugin execute functions.
pub type GuestValueMap = BTreeMap<String, GuestValue>;

// ---------------------------------------------------------------------------
// Accessor methods
// ---------------------------------------------------------------------------

impl GuestValue {
    /// Returns `true` if this value is [`GuestValue::Null`].
    pub fn is_null(&self) -> bool {
        matches!(self, GuestValue::Null)
    }

    /// Returns the boolean if this is a [`GuestValue::Bool`].
    pub fn as_bool(&self) -> Option<bool> {
        match self {
            GuestValue::Bool(b) => Some(*b),
            _ => None,
        }
    }

    /// Returns the `i64` if this is a [`GuestValue::Integer`].
    pub fn as_i64(&self) -> Option<i64> {
        match self {
            GuestValue::Integer(n) => Some(*n),
            _ => None,
        }
    }

    /// Returns the `f64` if this is a [`GuestValue::Float`].
    ///
    /// Note: also converts [`GuestValue::Integer`] to `f64` for convenience.
    pub fn as_f64(&self) -> Option<f64> {
        match self {
            GuestValue::Float(f) => Some(*f),
            GuestValue::Integer(n) => Some(*n as f64),
            _ => None,
        }
    }

    /// Returns the string slice if this is a [`GuestValue::String`].
    pub fn as_str(&self) -> Option<&str> {
        match self {
            GuestValue::String(s) => Some(s.as_str()),
            _ => None,
        }
    }

    /// Returns the array slice if this is a [`GuestValue::Array`].
    pub fn as_array(&self) -> Option<&[GuestValue]> {
        match self {
            GuestValue::Array(arr) => Some(arr.as_slice()),
            _ => None,
        }
    }

    /// Returns the object map if this is a [`GuestValue::Object`].
    pub fn as_object(&self) -> Option<&BTreeMap<String, GuestValue>> {
        match self {
            GuestValue::Object(map) => Some(map),
            _ => None,
        }
    }
}

// ---------------------------------------------------------------------------
// From impls for common Rust types -> GuestValue
// ---------------------------------------------------------------------------

impl From<bool> for GuestValue {
    fn from(v: bool) -> Self {
        GuestValue::Bool(v)
    }
}

impl From<i64> for GuestValue {
    fn from(v: i64) -> Self {
        GuestValue::Integer(v)
    }
}

impl From<i32> for GuestValue {
    fn from(v: i32) -> Self {
        GuestValue::Integer(i64::from(v))
    }
}

impl From<f64> for GuestValue {
    fn from(v: f64) -> Self {
        GuestValue::Float(v)
    }
}

impl From<f32> for GuestValue {
    fn from(v: f32) -> Self {
        GuestValue::Float(f64::from(v))
    }
}

impl From<String> for GuestValue {
    fn from(v: String) -> Self {
        GuestValue::String(v)
    }
}

impl From<&str> for GuestValue {
    fn from(v: &str) -> Self {
        GuestValue::String(v.to_owned())
    }
}

impl From<Vec<GuestValue>> for GuestValue {
    fn from(v: Vec<GuestValue>) -> Self {
        GuestValue::Array(v)
    }
}

impl From<BTreeMap<String, GuestValue>> for GuestValue {
    fn from(v: BTreeMap<String, GuestValue>) -> Self {
        GuestValue::Object(v)
    }
}

// ---------------------------------------------------------------------------
// serde_json::Value <-> GuestValue conversions
// ---------------------------------------------------------------------------

impl From<serde_json::Value> for GuestValue {
    fn from(v: serde_json::Value) -> Self {
        match v {
            serde_json::Value::Null => GuestValue::Null,
            serde_json::Value::Bool(b) => GuestValue::Bool(b),
            serde_json::Value::Number(n) => {
                if let Some(i) = n.as_i64() {
                    GuestValue::Integer(i)
                } else if let Some(f) = n.as_f64() {
                    GuestValue::Float(f)
                } else {
                    // Fallback: should not happen with well-formed JSON
                    GuestValue::Null
                }
            },
            serde_json::Value::String(s) => GuestValue::String(s),
            serde_json::Value::Array(arr) => {
                GuestValue::Array(arr.into_iter().map(GuestValue::from).collect())
            },
            serde_json::Value::Object(map) => GuestValue::Object(
                map.into_iter()
                    .map(|(k, v)| (k, GuestValue::from(v)))
                    .collect(),
            ),
        }
    }
}

impl From<GuestValue> for serde_json::Value {
    fn from(v: GuestValue) -> Self {
        match v {
            GuestValue::Null => serde_json::Value::Null,
            GuestValue::Bool(b) => serde_json::Value::Bool(b),
            GuestValue::Integer(n) => serde_json::Value::Number(n.into()),
            GuestValue::Float(f) => {
                // serde_json::Number::from_f64 returns None for NaN/Inf
                match serde_json::Number::from_f64(f) {
                    Some(n) => serde_json::Value::Number(n),
                    None => serde_json::Value::Null,
                }
            },
            GuestValue::String(s) => serde_json::Value::String(s),
            GuestValue::Array(arr) => {
                serde_json::Value::Array(arr.into_iter().map(serde_json::Value::from).collect())
            },
            GuestValue::Object(map) => serde_json::Value::Object(
                map.into_iter()
                    .map(|(k, v)| (k, serde_json::Value::from(v)))
                    .collect(),
            ),
        }
    }
}

// ---------------------------------------------------------------------------
// Display
// ---------------------------------------------------------------------------

impl std::fmt::Display for GuestValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            GuestValue::Null => write!(f, "null"),
            GuestValue::Bool(b) => write!(f, "{b}"),
            GuestValue::Integer(n) => write!(f, "{n}"),
            GuestValue::Float(v) => write!(f, "{v}"),
            GuestValue::String(s) => write!(f, "{s}"),
            GuestValue::Array(arr) => {
                let json = serde_json::to_string(arr).unwrap_or_else(|_| "[]".to_owned());
                write!(f, "{json}")
            },
            GuestValue::Object(map) => {
                let json = serde_json::to_string(map).unwrap_or_else(|_| "{}".to_owned());
                write!(f, "{json}")
            },
        }
    }
}

// ---------------------------------------------------------------------------
// Unit tests
// ---------------------------------------------------------------------------

#[cfg(test)]
#[allow(
    clippy::panic,
    clippy::expect_used,
    clippy::unwrap_used,
    clippy::approx_constant,
    clippy::doc_markdown,
    clippy::redundant_closure,
    clippy::unnecessary_get_then_check,
    clippy::double_comparisons,
    clippy::manual_let_else,
    clippy::result_large_err,
    clippy::bool_assert_comparison,
    clippy::needless_pass_by_value,
    clippy::unnecessary_cast,
    dead_code
)]
mod tests {
    use super::*;

    // -- Construction and accessor tests --

    #[test]
    fn null_value() {
        let v = GuestValue::Null;
        assert!(v.is_null());
        assert!(v.as_bool().is_none());
        assert!(v.as_i64().is_none());
        assert!(v.as_f64().is_none());
        assert!(v.as_str().is_none());
        assert!(v.as_array().is_none());
        assert!(v.as_object().is_none());
    }

    #[test]
    fn bool_value() {
        let v = GuestValue::Bool(true);
        assert!(!v.is_null());
        assert_eq!(v.as_bool(), Some(true));
        assert!(v.as_i64().is_none());
    }

    #[test]
    fn integer_value() {
        let v = GuestValue::Integer(42);
        assert_eq!(v.as_i64(), Some(42));
        // as_f64 also works on integers
        assert_eq!(v.as_f64(), Some(42.0));
        assert!(v.as_str().is_none());
    }

    #[test]
    fn float_value() {
        let v = GuestValue::Float(3.14);
        assert_eq!(v.as_f64(), Some(3.14));
        assert!(v.as_i64().is_none());
    }

    #[test]
    fn string_value() {
        let v = GuestValue::String("hello".into());
        assert_eq!(v.as_str(), Some("hello"));
        assert!(v.as_bool().is_none());
    }

    #[test]
    fn array_value() {
        let arr = vec![GuestValue::Integer(1), GuestValue::Integer(2)];
        let v = GuestValue::Array(arr.clone());
        assert_eq!(v.as_array(), Some(arr.as_slice()));
        assert!(v.as_object().is_none());
    }

    #[test]
    fn object_value() {
        let mut map = BTreeMap::new();
        map.insert("key".to_owned(), GuestValue::String("val".into()));
        let v = GuestValue::Object(map.clone());
        assert_eq!(v.as_object(), Some(&map));
        assert!(v.as_array().is_none());
    }

    // -- From impls for primitive types --

    #[test]
    fn from_bool() {
        let v: GuestValue = true.into();
        assert_eq!(v, GuestValue::Bool(true));
    }

    #[test]
    fn from_i64() {
        let v: GuestValue = 99i64.into();
        assert_eq!(v, GuestValue::Integer(99));
    }

    #[test]
    fn from_i32() {
        let v: GuestValue = 42i32.into();
        assert_eq!(v, GuestValue::Integer(42));
    }

    #[test]
    fn from_f64() {
        let v: GuestValue = 2.71f64.into();
        assert_eq!(v, GuestValue::Float(2.71));
    }

    #[test]
    fn from_f32() {
        let v: GuestValue = 1.5f32.into();
        assert_eq!(v, GuestValue::Float(1.5));
    }

    #[test]
    fn from_string() {
        let v: GuestValue = String::from("test").into();
        assert_eq!(v, GuestValue::String("test".into()));
    }

    #[test]
    fn from_str_ref() {
        let v: GuestValue = "test".into();
        assert_eq!(v, GuestValue::String("test".into()));
    }

    #[test]
    fn from_vec() {
        let vec = vec![GuestValue::Null, GuestValue::Bool(true)];
        let v: GuestValue = vec.clone().into();
        assert_eq!(v, GuestValue::Array(vec));
    }

    #[test]
    fn from_btreemap() {
        let mut map = BTreeMap::new();
        map.insert("a".to_owned(), GuestValue::Integer(1));
        let v: GuestValue = map.clone().into();
        assert_eq!(v, GuestValue::Object(map));
    }

    // -- serde_json::Value <-> GuestValue --

    #[test]
    fn from_json_null() {
        let gv = GuestValue::from(serde_json::Value::Null);
        assert_eq!(gv, GuestValue::Null);
    }

    #[test]
    fn from_json_bool() {
        let gv = GuestValue::from(serde_json::Value::Bool(false));
        assert_eq!(gv, GuestValue::Bool(false));
    }

    #[test]
    fn from_json_integer() {
        let gv = GuestValue::from(serde_json::json!(42));
        assert_eq!(gv, GuestValue::Integer(42));
    }

    #[test]
    fn from_json_float() {
        let gv = GuestValue::from(serde_json::json!(3.14));
        assert_eq!(gv, GuestValue::Float(3.14));
    }

    #[test]
    fn from_json_string() {
        let gv = GuestValue::from(serde_json::json!("hello"));
        assert_eq!(gv, GuestValue::String("hello".into()));
    }

    #[test]
    fn from_json_array() {
        let gv = GuestValue::from(serde_json::json!([1, "two", null]));
        let arr = gv.as_array().expect("should be array");
        assert_eq!(arr.len(), 3);
        assert_eq!(arr[0], GuestValue::Integer(1));
        assert_eq!(arr[1], GuestValue::String("two".into()));
        assert_eq!(arr[2], GuestValue::Null);
    }

    #[test]
    fn from_json_object() {
        let gv = GuestValue::from(serde_json::json!({"key": "value", "num": 10}));
        let obj = gv.as_object().expect("should be object");
        assert_eq!(obj.get("key"), Some(&GuestValue::String("value".into())));
        assert_eq!(obj.get("num"), Some(&GuestValue::Integer(10)));
    }

    #[test]
    fn to_json_null() {
        let jv: serde_json::Value = GuestValue::Null.into();
        assert_eq!(jv, serde_json::Value::Null);
    }

    #[test]
    fn to_json_bool() {
        let jv: serde_json::Value = GuestValue::Bool(true).into();
        assert_eq!(jv, serde_json::json!(true));
    }

    #[test]
    fn to_json_integer() {
        let jv: serde_json::Value = GuestValue::Integer(100).into();
        assert_eq!(jv, serde_json::json!(100));
    }

    #[test]
    fn to_json_float() {
        let jv: serde_json::Value = GuestValue::Float(2.5).into();
        assert_eq!(jv, serde_json::json!(2.5));
    }

    #[test]
    fn to_json_float_nan_becomes_null() {
        let jv: serde_json::Value = GuestValue::Float(f64::NAN).into();
        assert_eq!(jv, serde_json::Value::Null);
    }

    #[test]
    fn to_json_float_inf_becomes_null() {
        let jv: serde_json::Value = GuestValue::Float(f64::INFINITY).into();
        assert_eq!(jv, serde_json::Value::Null);
    }

    #[test]
    fn to_json_string() {
        let jv: serde_json::Value = GuestValue::String("abc".into()).into();
        assert_eq!(jv, serde_json::json!("abc"));
    }

    #[test]
    fn to_json_array() {
        let gv = GuestValue::Array(vec![GuestValue::Integer(1), GuestValue::Bool(false)]);
        let jv: serde_json::Value = gv.into();
        assert_eq!(jv, serde_json::json!([1, false]));
    }

    #[test]
    fn to_json_object() {
        let mut map = BTreeMap::new();
        map.insert("a".to_owned(), GuestValue::String("b".into()));
        let gv = GuestValue::Object(map);
        let jv: serde_json::Value = gv.into();
        assert_eq!(jv, serde_json::json!({"a": "b"}));
    }

    // -- Serde round-trip --

    #[test]
    fn serde_roundtrip_null() {
        let original = GuestValue::Null;
        let json = serde_json::to_string(&original).expect("serialize");
        let deserialized: GuestValue = serde_json::from_str(&json).expect("deserialize");
        assert_eq!(deserialized, original);
    }

    #[test]
    fn serde_roundtrip_bool() {
        let original = GuestValue::Bool(true);
        let json = serde_json::to_string(&original).expect("serialize");
        let deserialized: GuestValue = serde_json::from_str(&json).expect("deserialize");
        assert_eq!(deserialized, original);
    }

    #[test]
    fn serde_roundtrip_integer() {
        let original = GuestValue::Integer(i64::MAX);
        let json = serde_json::to_string(&original).expect("serialize");
        let deserialized: GuestValue = serde_json::from_str(&json).expect("deserialize");
        assert_eq!(deserialized, original);
    }

    #[test]
    fn serde_roundtrip_float() {
        let original = GuestValue::Float(1.23456789);
        let json = serde_json::to_string(&original).expect("serialize");
        let deserialized: GuestValue = serde_json::from_str(&json).expect("deserialize");
        assert_eq!(deserialized, original);
    }

    #[test]
    fn serde_roundtrip_string() {
        let original = GuestValue::String("hello world".into());
        let json = serde_json::to_string(&original).expect("serialize");
        let deserialized: GuestValue = serde_json::from_str(&json).expect("deserialize");
        assert_eq!(deserialized, original);
    }

    #[test]
    fn serde_roundtrip_nested_object() {
        let mut inner = BTreeMap::new();
        inner.insert("x".to_owned(), GuestValue::Integer(1));
        inner.insert(
            "y".to_owned(),
            GuestValue::Array(vec![GuestValue::Null, GuestValue::Bool(false)]),
        );
        let mut outer = BTreeMap::new();
        outer.insert("nested".to_owned(), GuestValue::Object(inner));
        outer.insert("flag".to_owned(), GuestValue::Bool(true));

        let original = GuestValue::Object(outer);
        let json = serde_json::to_string(&original).expect("serialize");
        let deserialized: GuestValue = serde_json::from_str(&json).expect("deserialize");
        assert_eq!(deserialized, original);
    }

    // -- GuestValueMap serde round-trip --

    #[test]
    fn guest_value_map_serde_roundtrip() {
        let mut map = GuestValueMap::new();
        map.insert("name".into(), GuestValue::String("test".into()));
        map.insert("count".into(), GuestValue::Integer(5));
        map.insert(
            "tags".into(),
            GuestValue::Array(vec![
                GuestValue::String("a".into()),
                GuestValue::String("b".into()),
            ]),
        );

        let json = serde_json::to_string(&map).expect("serialize");
        let deserialized: GuestValueMap = serde_json::from_str(&json).expect("deserialize");
        assert_eq!(deserialized, map);
    }

    // -- Display --

    #[test]
    fn display_null() {
        assert_eq!(GuestValue::Null.to_string(), "null");
    }

    #[test]
    fn display_bool() {
        assert_eq!(GuestValue::Bool(true).to_string(), "true");
    }

    #[test]
    fn display_integer() {
        assert_eq!(GuestValue::Integer(42).to_string(), "42");
    }

    #[test]
    fn display_float() {
        assert_eq!(GuestValue::Float(3.14).to_string(), "3.14");
    }

    #[test]
    fn display_string() {
        assert_eq!(GuestValue::String("hello".into()).to_string(), "hello");
    }

    // -- From<serde_json::Value> round-trip consistency --

    #[test]
    fn json_roundtrip_via_from_impls() {
        let json_original = serde_json::json!({
            "name": "test",
            "count": 42,
            "active": true,
            "data": [1, 2, 3],
            "meta": {"nested": "value"},
            "empty": null
        });
        let guest_value = GuestValue::from(json_original.clone());
        let json_back: serde_json::Value = guest_value.into();
        assert_eq!(json_back, json_original);
    }

    // -- Edge cases --

    #[test]
    fn empty_string() {
        let v = GuestValue::String(String::new());
        assert_eq!(v.as_str(), Some(""));
    }

    #[test]
    fn empty_array() {
        let v = GuestValue::Array(vec![]);
        let arr = v.as_array().expect("should be array");
        assert!(arr.is_empty());
    }

    #[test]
    fn empty_object() {
        let v = GuestValue::Object(BTreeMap::new());
        let obj = v.as_object().expect("should be object");
        assert!(obj.is_empty());
    }

    #[test]
    fn large_integer_boundary() {
        let v = GuestValue::Integer(i64::MAX);
        assert_eq!(v.as_i64(), Some(i64::MAX));

        let v = GuestValue::Integer(i64::MIN);
        assert_eq!(v.as_i64(), Some(i64::MIN));
    }

    #[test]
    fn negative_integer() {
        let v = GuestValue::Integer(-100);
        assert_eq!(v.as_i64(), Some(-100));
        assert_eq!(v.as_f64(), Some(-100.0));
    }

    #[test]
    fn clone_equality() {
        let original = GuestValue::Object(BTreeMap::from([
            ("a".into(), GuestValue::Integer(1)),
            ("b".into(), GuestValue::String("two".into())),
        ]));
        let cloned = original.clone();
        assert_eq!(original, cloned);
    }
}
