// Issue #96: Zero-allocation serde helpers for Binance trade deserialization
//!
//! Binance sends price and quantity as JSON strings (e.g., `"112070.01000000"`).
//! Instead of deserializing to `String` then parsing, these helpers deserialize
//! directly to `FixedPoint` — eliminating per-trade String allocations (~4000/sec on WS).

use opendeviationbar_core::FixedPoint;
use serde::Deserializer;
use serde::de::{self, Visitor};
use std::fmt;

struct FixedPointVisitor;

impl<'de> Visitor<'de> for FixedPointVisitor {
    type Value = FixedPoint;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        formatter.write_str("a decimal string (e.g., \"112070.01000000\")")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        FixedPoint::from_str(v).map_err(de::Error::custom)
    }

    fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
    where
        E: de::Error,
    {
        FixedPoint::from_str(v).map_err(de::Error::custom)
    }
}

/// Deserialize a JSON string directly into `FixedPoint` without intermediate String allocation.
///
/// Usage: `#[serde(deserialize_with = "deserialize_fixedpoint_from_str")]`
pub fn deserialize_fixedpoint_from_str<'de, D>(deserializer: D) -> Result<FixedPoint, D::Error>
where
    D: Deserializer<'de>,
{
    deserializer.deserialize_str(FixedPointVisitor)
}

#[cfg(test)]
mod tests {
    use super::*;
    use opendeviationbar_core::fixed_point::SCALE;

    #[derive(serde::Deserialize)]
    struct Wrapper {
        #[serde(deserialize_with = "super::deserialize_fixedpoint_from_str")]
        value: FixedPoint,
    }

    /// Oracle test: serde deserializer must produce bit-identical FixedPoint values
    /// to the existing FixedPoint::from_str() path.
    #[test]
    fn oracle_fixedpoint_deser_vs_from_str() {
        let test_vectors: &[(&str, i64)] = &[
            ("112070.01000000", 11207001000000),
            ("0.01328000", 1328000),
            ("0.00000001", 1),
            ("99999999.99999999", 9999999999999999),
            ("0", 0),
            ("-1.50000000", -150000000),
            ("1", SCALE),
            ("0.1", 10000000),
            ("0.12345678", 12345678),
        ];

        for &(json_str, expected_i64) in test_vectors {
            let from_str_result = FixedPoint::from_str(json_str).unwrap();
            assert_eq!(
                from_str_result.0, expected_i64,
                "ground truth mismatch for {json_str}"
            );

            let json = format!(r#"{{"value": "{json_str}"}}"#);
            let wrapper: Wrapper = serde_json::from_str(&json).unwrap();
            assert_eq!(
                wrapper.value.0, from_str_result.0,
                "DIVERGENCE at '{json_str}': serde={} vs from_str={}",
                wrapper.value.0, from_str_result.0
            );
        }
    }

    #[test]
    fn deser_fixedpoint_error_cases() {
        #[derive(serde::Deserialize)]
        struct W {
            #[serde(deserialize_with = "super::deserialize_fixedpoint_from_str")]
            _v: FixedPoint,
        }

        assert!(serde_json::from_str::<W>(r#"{"_v": ""}"#).is_err());
        assert!(serde_json::from_str::<W>(r#"{"_v": "1.123456789"}"#).is_err());
        assert!(serde_json::from_str::<W>(r#"{"_v": "abc"}"#).is_err());
    }
}
