use crate::__private::ExpectOpSerialize;
use crate::expect_core::ExpectOpMarkerId;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Map;
use serde_json::Value;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;

thread_local! {
    static SERIALIZATION_IS_AT_ROOT: AtomicUsize = const { AtomicUsize::new(0) };
}

#[doc(hidden)]
#[derive(Debug, Serialize, Deserialize)]
pub struct SerializeExpectOp {
    magic_id: ExpectOpMarkerId,
    pub inner: Box<dyn ExpectOpSerialize>,
}

impl SerializeExpectOp {
    pub(crate) fn has_magic_id(value: &Value) -> bool {
        value.as_object().is_some_and(Self::has_object_magic_id)
    }

    pub(crate) fn has_object_magic_id(object: &Map<String, Value>) -> bool {
        object
            .get("magic_id")
            .is_some_and(ExpectOpMarkerId::is_magic_id_value)
    }

    pub fn serialize<E, S>(expect_op: &E, serializer: S) -> Result<S::Ok, S::Error>
    where
        E: ExpectOpSerialize + Clone + crate::__private::serde_trampoline::Serialize,
        S: serde::Serializer,
    {
        let is_at_root =
            SERIALIZATION_IS_AT_ROOT.with(|is_at_root| is_at_root.fetch_add(1, Ordering::Acquire));

        let result = if is_at_root == 0 {
            SerializeExpectOp::new(Box::new(expect_op.clone())).serialize(serializer)
        } else {
            crate::__private::serde_trampoline::Serialize::serialize(expect_op, serializer)
        };

        SERIALIZATION_IS_AT_ROOT.with(|is_at_root| {
            is_at_root.fetch_sub(1, Ordering::Release);
        });

        result
    }

    pub fn new(inner: Box<dyn ExpectOpSerialize>) -> Self {
        Self {
            magic_id:
                ExpectOpMarkerId::__ExpectJson_MarkerId_0ABDBD14_93D1_4D73_8E26_0177D8A280A4__,
            inner,
        }
    }

    pub fn maybe_parse(value: &Value) -> Option<Self> {
        if !Self::has_magic_id(value) {
            return None;
        }

        let obj = serde_json::from_value(value.clone())
            .expect("Failed to decode internal expect structure from Json");
        Some(obj)
    }

    pub fn maybe_parse_from_obj(object: &Map<String, Value>) -> Option<Box<dyn ExpectOpSerialize>> {
        if !Self::has_object_magic_id(object) {
            return None;
        }

        let inner = object.get("inner")?;
        let obj = serde_json::from_value(inner.clone())
            .expect("Failed to decode internal expect structure from Json");
        Some(obj)
    }
}

#[cfg(test)]
mod test_serialize {
    use crate::expect;
    use pretty_assertions::assert_eq;
    use serde_json::json;

    #[test]
    fn it_should_serialize_into_expected_structure_with_expect_id_marker() {
        let output = json!(expect::array().contains([1, 2, 3]));
        assert_eq!(
            output,
            json!({
                "magic_id": "__ExpectJson_MarkerId_0ABDBD14_93D1_4D73_8E26_0177D8A280A4__",
                "inner": {
                    "sub_ops": [
                        {
                            "Contains": [
                                1,
                                2,
                                3,
                            ],
                        },
                    ],
                    "type": "ExpectArray"
                },
            })
        );
    }

    #[test]
    fn it_should_serialize_an_op_within_an_inner_op_inside() {
        let output = json!(expect::array().contains([expect::object().contains(json!({
            "name": "John",
            "age": 30,
        }))]));

        assert_eq!(
            output,
            json!({
                "magic_id": "__ExpectJson_MarkerId_0ABDBD14_93D1_4D73_8E26_0177D8A280A4__",
                "inner": {
                    "sub_ops": [
                        {
                            "Contains": [
                                {
                                    "magic_id": "__ExpectJson_MarkerId_0ABDBD14_93D1_4D73_8E26_0177D8A280A4__",
                                    "inner": {
                                        "sub_ops": [
                                            {
                                                "Contains": {
                                                    "age": 30,
                                                    "name": "John",
                                                },
                                            },
                                        ],
                                        "type": "ExpectObject"
                                    },
                                },
                            ],
                        },
                    ],
                    "type": "ExpectArray"
                },
            })
        );
    }
}
