//! Payloads module for {{stinger.name}} IPC
//! 
//! Contains all the data structures, enums, and return codes used by the {{stinger.name}} IPC system.

/*
DO NOT MODIFY THIS FILE .  It is automatically generated and changes will be over-written
on the next generation.

It contains enumerations used by the {{stinger.name}} interface.
*/

{%if stinger.enums | length > 0 %}
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use std::fmt;
{%endif%}
use serde::{Deserialize, Serialize};


pub mod base64_binary_format {
    use serde::{Deserialize, Deserializer, Serializer};
    use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
    use base64::Engine;

    pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let b64_string = BASE64_STANDARD.encode(bytes);
        serializer.serialize_str(&b64_string)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let b64_string = String::deserialize(deserializer)?;
        BASE64_STANDARD
            .decode(b64_string.as_bytes())
            .map_err(serde::de::Error::custom)
    }

    // For Option<Vec<u8>>
    pub fn serialize_option<S>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match bytes {
            Some(b) => serialize(b, serializer),
            None => serializer.serialize_none(),
        }
    }

    pub fn deserialize_option<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let opt = Option::<String>::deserialize(deserializer)?;
        match opt {
            Some(b64_string) => {
                let decoded = BASE64_STANDARD
                    .decode(b64_string.as_bytes())
                    .map_err(serde::de::Error::custom)?;
                Ok(Some(decoded))
            }
            None => Ok(None),
        }
    }
}

// Helper functions for DateTime serialization/deserialization
pub mod datetime_iso_format {
    use chrono::{DateTime, Utc};
    use serde::{Deserialize, Deserializer, Serializer};

    pub fn serialize<S>(dt: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let iso_string = dt.to_rfc3339();
        serializer.serialize_str(&iso_string)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let iso_string = String::deserialize(deserializer)?;
        DateTime::parse_from_rfc3339(&iso_string)
            .map(|dt| dt.with_timezone(&Utc))
            .map_err(serde::de::Error::custom)
    }
}

pub mod duration_iso_format {
    use chrono::Duration;
    use iso8601_duration::Duration as IsoDuration;
    use serde::{Deserializer, Serializer, Deserialize};

    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let seconds = duration.num_seconds();
        let iso_string = format!("PT{}S", seconds);
        serializer.serialize_str(&iso_string)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
    where
        D: Deserializer<'de>,
    {
        let iso_string = String::deserialize(deserializer)?;
        let iso_dur: IsoDuration = iso_string.parse::<IsoDuration>()
            .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?;
        let std_duration: std::time::Duration = iso_dur.to_std()
            .ok_or_else(|| serde::de::Error::custom("Failed to convert ISO duration to std::time::Duration"))?;
        chrono::Duration::from_std(std_duration).map_err(serde::de::Error::custom)
    }
}


#[allow(dead_code)]
#[derive(Debug)]
pub enum MethodReturnCode { {%-for rc_i, rc_name in stinger.method_return_codes.items()%}
    {{rc_name | UpperCamelCase}}{%if not loop.first%}(String){%endif%},
    {%-endfor%}
}

impl MethodReturnCode {
    pub fn from_code(code: u32, message: Option<String>) -> Self {
        match code {
            {%-for rc_i, rc_name in stinger.method_return_codes.items()%}
            {{rc_i}} => MethodReturnCode::{{rc_name | UpperCamelCase}}{%if not loop.first%}(message.unwrap_or_default()){%endif%},
            {%-endfor%}
            _ => MethodReturnCode::UnknownError(message.unwrap_or_default()),
        }
    }

    pub fn to_code(&self) -> (u32, Option<String>) {
        match self {
            {%-for rc_i, rc_name in stinger.method_return_codes.items() %}
            MethodReturnCode::{{rc_name | UpperCamelCase}}{%if not loop.first%}(msg){%endif%} => ({{rc_i}}, {%if not loop.first%}Some(msg.clone()){%else%}None{%endif%}),
            {%-endfor%}
        }
    }
}

// --- ENUMERATIONS ---

{%for ie_name, ie in stinger.enums.items() %}
#[repr(u32)]
#[derive(Debug, FromPrimitive, ToPrimitive, Clone, Serialize, Deserialize, PartialEq)]
#[serde(into = "u32", try_from = "u32")]
pub enum {{ie_name | UpperCamelCase }} {
    {%-for value in ie.values %}
    {{value | UpperCamelCase}} = {{loop.index}}{%if not loop.last%},{%endif%}
    {%-endfor%}
}

#[allow(dead_code)]
impl {{ie_name | UpperCamelCase }} {
    pub fn from_u32(value: u32) -> Option<Self> {
        FromPrimitive::from_u32(value)
    }
}

impl From<{{ie_name | UpperCamelCase }}> for u32 {
    fn from(s: {{ie_name | UpperCamelCase }}) -> u32 {
        s as u32
    }
}

{#
impl From<u32> for {{ie_name | UpperCamelCase }} {
    fn from(s: u32) -> {{ie_name | UpperCamelCase }} {
        {{ie_name | UpperCamelCase }}::from_u32(s).unwrap()
    }
}#}

impl TryFrom<u32> for {{ie_name | UpperCamelCase }} {
    type Error = String;
    fn try_from(v: u32) -> Result<Self, Self::Error> {
        {{ie_name | UpperCamelCase }}::from_u32(v).ok_or_else(|| format!("Invalid {{ie_name | UpperCamelCase }} value: {}", v))
    }
}


impl fmt::Display for {{ie_name | UpperCamelCase }} {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
       write!(f, "{:?}", self)
    }
}
{%endfor%}

// --- INTERFACE STRUCTURES ---

{%for struct_name, struct in stinger.structs.items() %}
#[allow(dead_code, non_snake_case)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct {{struct_name | UpperCamelCase}} {
    {%-for arg in struct.members%}
    {%if arg.description%} /// {{arg.description}}{%endif%}
    {%-if arg.name != (arg.name | snake_case) -%}#[serde(rename = "{{arg.name}}")]{%-endif%}
    {%-if arg.arg_type.name.lower() == 'primitive'%}
    pub {{arg.name|snake_case}}: {{arg.rust_type}},
    {%-elif arg.arg_type.name.lower() == 'enum'%}
    pub {{arg.name|snake_case}}: {{arg.rust_local_type}},
    {%elif arg.arg_type.name.lower() == 'datetime'%}
    #[serde(with = "datetime_iso_format")]
    pub {{arg.name|snake_case}}: chrono::DateTime<chrono::Utc>,
    {%elif arg.arg_type.name.lower() == 'duration'%}
    #[serde(with = "duration_iso_format")]
    pub {{arg.name|snake_case}}: chrono::Duration,
    {%elif arg.arg_type.name.lower() == 'binary'%}
    #[serde(with = "base64_binary_format")]
    pub {{arg.name|snake_case}}: Vec<u8>,
    {%endif-%}
    {%endfor%}
}
{%-endfor%}

// ---- METHODS ----

{%for method_name, method in stinger.methods.items()%}

// Structures for `{{method_name}}` method

#[allow(dead_code, non_snake_case)]
#[derive(Clone, Debug, Serialize, Deserialize)]
/// Request Object for `{{method_name}}` method.
pub struct {{method_name | UpperCamelCase}}RequestObject {
    {%-for arg in method.arg_list%}
    {%if arg.description%} /// {{arg.description}}{%endif%}
    {%-if arg.name != (arg.name | snake_case) -%}#[serde(rename = "{{arg.name}}")]{%-endif%}
    {%-if arg.arg_type.name.lower() == 'primitive'%}
    pub {{arg.name}}: {{arg.rust_type}},
    {%-elif arg.arg_type.name.lower() == 'enum'%}
    pub {{arg.name}}: {{arg.rust_local_type}},
    {%-elif arg.arg_type.name.lower() == 'datetime'%}
    #[serde(with = "datetime_iso_format")]
    pub {{arg.name}}: chrono::DateTime<chrono::Utc>,
    {%-elif arg.arg_type.name.lower() == 'duration'%}
    #[serde(with = "duration_iso_format")]
    pub {{arg.name}}: chrono::Duration,
    {%-elif arg.arg_type.name.lower() == 'struct'%}
    pub {{arg.name}}: {{arg.rust_type}},
    {%-elif arg.arg_type.name.lower() == 'binary'%}
    #[serde(with = "base64_binary_format")]
    pub {{arg.name}}: Vec<u8>,
    {%else%}
    TEMPLATE ERROR CASE NOT HANDLED for {{arg.arg_type.name}} - please report a bug.
    {%endif%}
    {%endfor%}
}


#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
/// Return Object for `{{method_name}}` method.
pub struct {{method.return_value_name | UpperCamelCase}} {
{%-for arg in method.return_arg_list %}
    {%if arg.arg_type.name.lower() == 'datetime'%}#[serde(with = "datetime_iso_format")]
    {%-elif arg.arg_type.name.lower() == 'duration'%}#[serde(with = "duration_iso_format")]
    {%-elif arg.arg_type.name.lower() == 'binary'-%}#[serde(with = "base64_binary_format")]{%endif%}
    {%-if arg.name != (arg.name | snake_case) -%}#[serde(rename = "{{arg.name}}")]{%-endif%}
    {%if arg.description%} /// {{arg.description}}{%endif%}
    pub {{arg.name|snake_case}}: {{arg.rust_local_type}},
{%-endfor%}
}
{%-endfor%}

// ---- SIGNALS ----
{%for sig_name, sig in stinger.signals.items()%}
// Structures for `{{sig_name}}` signal
#[allow(dead_code, non_snake_case)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct {{sig_name | UpperCamelCase}}SignalPayload {
    {%-for arg in sig.arg_list%}
    {%if arg.description%} /// {{arg.description}}{%endif%}
    {%-if arg.name != (arg.name | snake_case) -%}#[serde(rename = "{{arg.name}}")]{%-endif%}
    {%-if arg.arg_type.name.lower() == 'primitive'%}
    pub {{arg.name}}: {{arg.rust_type}}, 
    {%-elif arg.arg_type.name.lower() == 'struct'%}
    pub {{arg.name}}: {{arg.rust_type}}, 
    {%-elif arg.arg_type.name.lower() == 'enum'%}
    pub {{arg.name}}: {{arg.rust_local_type}}, 
    {%-elif arg.arg_type.name.lower() == 'datetime'%}
    #[serde(with = "datetime_iso_format")]
    pub {{arg.name}}: chrono::DateTime<chrono::Utc>,
    {%-elif arg.arg_type.name.lower() == 'duration'%}
    #[serde(with = "duration_iso_format")]
    pub {{arg.name}}: chrono::Duration, 
    {%-elif arg.arg_type.name.lower() == 'binary'%} 
    #[serde(with = "base64_binary_format")]
    pub {{arg.name}}: Vec<u8>,
    {%-endif-%}
    {%endfor%}
}
{%endfor%}

{%for prop_name, prop in stinger.properties.items()%}
// `{{prop_name}}` property structure.
#[allow(dead_code, non_snake_case)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct {{prop_name | UpperCamelCase}}Property {
    {%for arg in prop.arg_list%}
    {%-if arg.arg_type.name.lower() == 'primitive' -%}
    pub {{arg.name}}: {{arg.rust_type}},
    {%elif arg.arg_type.name.lower() == 'enum'-%}
    pub {{arg.name}}: {{arg.rust_local_type}},
    {%elif arg.arg_type.name.lower() == 'struct'-%}
    pub {{arg.name}}: {{arg.rust_local_type}},
    {%elif arg.arg_type.name.lower() == 'datetime'%}
    #[serde(with = "datetime_iso_format")]
    pub {{arg.name}}: chrono::DateTime<chrono::Utc>,
    {%elif arg.arg_type.name.lower() == 'duration'%}
    #[serde(with = "duration_iso_format")]
    pub {{arg.name}}: chrono::Duration,
    {%elif arg.arg_type.name.lower() == 'binary'%}
    #[serde(with = "base64_binary_format")]
    pub {{arg.name}}: Vec<u8>,
    {%endif-%}
    {%endfor%}
}
{%endfor%}


#[cfg(test)]
mod tests {
    use super::*;
    use chrono::{DateTime, Utc};

    {%for prop_name, prop in stinger.properties.items()%}
    #[test]
    fn test_{{prop_name | snake_case}}_property_json_format() {
        // Test deserializing from a known JSON string
        let json_str = r#"{ {%-for arg in prop.arg_list%}
            "{{arg.name}}": {{arg.get_random_example_value(lang="json")}} {%if not loop.last%},{%endif%}
        {%endfor-%} }"#;

        let parsed: {{prop_name | UpperCamelCase}}Property = serde_json::from_str(json_str).unwrap();
    }
    {%endfor%}

    #[test]
    fn test_base64_binary_format_serialization() {
        use serde::{Deserialize, Serialize};
        
        #[derive(Serialize, Deserialize, Debug, PartialEq)]
        struct TestStruct {
            #[serde(with = "base64_binary_format")]
            data: Vec<u8>,
        }

        // Test with various binary data
        let test_data = vec![0x00, 0x01, 0x02, 0xFF, 0xFE, 0x42, 0x13, 0x37];
        let test_struct = TestStruct { data: test_data.clone() };

        // Test serialization
        let serialized = serde_json::to_string(&test_struct).unwrap();
        
        // The base64 encoded value of [0x00, 0x01, 0x02, 0xFF, 0xFE, 0x42, 0x13, 0x37] should be "AAEC//5CEzc="
        assert!(serialized.contains("AAEC//5CEzc="));

        // Test deserialization
        let deserialized: TestStruct = serde_json::from_str(&serialized).unwrap();
        assert_eq!(deserialized.data, test_data);
    }

    #[test]
    fn test_base64_binary_format_option_serialization() {
        use serde::{Deserialize, Serialize};
        
        #[derive(Serialize, Deserialize, Debug, PartialEq)]
        struct TestStruct {
            #[serde(with = "base64_binary_format")]
            #[serde(serialize_with = "base64_binary_format::serialize_option")]
            #[serde(deserialize_with = "base64_binary_format::deserialize_option")]
            data: Option<Vec<u8>>,
        }

        // Test with Some data
        let test_data = vec![0x48, 0x65, 0x6C, 0x6C, 0x6F]; // "Hello" in bytes
        let test_struct_some = TestStruct { data: Some(test_data.clone()) };

        let serialized_some = serde_json::to_string(&test_struct_some).unwrap();
        let deserialized_some: TestStruct = serde_json::from_str(&serialized_some).unwrap();
        assert_eq!(deserialized_some.data, Some(test_data));

        // Test with None
        let test_struct_none = TestStruct { data: None };
        let serialized_none = serde_json::to_string(&test_struct_none).unwrap();
        let deserialized_none: TestStruct = serde_json::from_str(&serialized_none).unwrap();
        assert_eq!(deserialized_none.data, None);
    }

    #[test]
    fn test_base64_binary_format_round_trip() {
        use serde::{Deserialize, Serialize};
        
        #[derive(Serialize, Deserialize, Debug, PartialEq)]
        struct BinaryData {
            #[serde(with = "base64_binary_format")]
            payload: Vec<u8>,
            name: String,
        }

        // Test with empty data
        let empty_data = BinaryData {
            payload: vec![],
            name: "empty".to_string(),
        };
        let json = serde_json::to_string(&empty_data).unwrap();
        let parsed: BinaryData = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed, empty_data);

        // Test with random binary data
        let random_data = BinaryData {
            payload: vec![0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE],
            name: "random".to_string(),
        };
        let json = serde_json::to_string(&random_data).unwrap();
        let parsed: BinaryData = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed, random_data);
    }
}
