use serde::{Deserialize, Serialize};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum SoapVersion {
    #[serde(rename = "soap_11")]
    Soap11,
    #[serde(rename = "soap_12")]
    Soap12,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SoapBindingStyle {
    DocumentLiteral,
    RpcLiteral,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SoapHeaderConfig {
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub namespace_uri: Option<String>,
    pub required: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub value_path: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SoapFaultContract {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub code_path: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message_path: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub detail_path: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct SoapOperationMetadata {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub input_part_names: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub output_part_names: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub namespaces: Vec<String>,
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use super::{
        SoapBindingStyle, SoapFaultContract, SoapHeaderConfig, SoapOperationMetadata, SoapVersion,
    };

    #[test]
    fn soap_metadata_serializes_compactly() {
        let value = serde_json::to_value(SoapOperationMetadata {
            input_part_names: vec!["LeadRequest".to_owned()],
            output_part_names: vec!["LeadResponse".to_owned()],
            namespaces: vec!["urn:crm".to_owned()],
        })
        .unwrap();

        assert_eq!(value["input_part_names"][0], "LeadRequest");
        assert_eq!(value["output_part_names"][0], "LeadResponse");
        assert_eq!(value["namespaces"][0], "urn:crm");
    }

    #[test]
    fn soap_supporting_types_roundtrip() {
        let value = json!({
            "version": "soap_12",
            "style": "document_literal",
            "header": {
                "name": "CorrelationId",
                "namespace_uri": "urn:crm",
                "required": true,
                "value_path": "$.mcp.correlation_id"
            },
            "fault": {
                "code_path": "$.Envelope.Body.Fault.Code.Value",
                "message_path": "$.Envelope.Body.Fault.Reason.Text",
                "detail_path": "$.Envelope.Body.Fault.Detail"
            }
        });

        let version: SoapVersion = serde_json::from_value(value["version"].clone()).unwrap();
        let style: SoapBindingStyle = serde_json::from_value(value["style"].clone()).unwrap();
        let header: SoapHeaderConfig = serde_json::from_value(value["header"].clone()).unwrap();
        let fault: SoapFaultContract = serde_json::from_value(value["fault"].clone()).unwrap();

        assert_eq!(version, SoapVersion::Soap12);
        assert_eq!(style, SoapBindingStyle::DocumentLiteral);
        assert_eq!(header.name, "CorrelationId");
        assert_eq!(
            fault.detail_path.as_deref(),
            Some("$.Envelope.Body.Fault.Detail")
        );
    }
}
