//! WebSocket message types for IDP communication.

use std::fmt;

use serde::de::{self, Deserializer, Visitor};
use serde::{Deserialize, Serialize};

/// WebSocket message from the IDP.
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type")]
#[non_exhaustive]
pub enum WsMessage {
    /// Verification status update
    #[serde(rename = "verification.status")]
    VerificationStatus { payload: VerificationStatusPayload },

    /// Error response
    #[serde(rename = "error")]
    Error { payload: ErrorPayload },
}

/// Verification status payload from IDP.
#[derive(Debug, Clone, Deserialize)]
pub struct VerificationStatusPayload {
    pub request_id: String,
    pub state: String,
    /// VP token - can be a single string or array from IDP (normalized to
    /// single string)
    #[serde(default, deserialize_with = "deserialize_string_or_vec")]
    pub vp_token: Option<String>,
    #[serde(default)]
    pub error: Option<String>,
}

/// Deserialize a field that can be either a string or array of strings.
/// Arrays are joined with `~` (standard VP token separator for multi-credential
/// presentations).
fn deserialize_string_or_vec<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
    D: Deserializer<'de>,
{
    struct StringOrVec;

    impl<'de> Visitor<'de> for StringOrVec {
        type Value = Option<String>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("a string, array of strings, or null")
        }

        fn visit_none<E>(self) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            Ok(None)
        }

        fn visit_unit<E>(self) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            Ok(None)
        }

        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            Ok(Some(value.to_owned()))
        }

        fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            Ok(Some(value))
        }

        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
        where
            A: de::SeqAccess<'de>,
        {
            let mut tokens: Vec<String> = Vec::new();
            while let Some(token) = seq.next_element::<String>()? {
                tokens.push(token);
            }
            if tokens.is_empty() {
                Ok(None)
            } else {
                // Join with `~` - standard VP token separator
                Ok(Some(tokens.join("~")))
            }
        }
    }

    deserializer.deserialize_any(StringOrVec)
}

/// Error payload from IDP.
#[derive(Debug, Clone, Deserialize)]
pub struct ErrorPayload {
    pub code: String,
    pub message: String,
}

/// Subscribe event sent to IDP.
#[derive(Debug, Clone, Serialize)]
pub struct VerificationSubscribeEvent {
    #[serde(rename = "type")]
    pub event_type: &'static str,
    pub payload: VerificationSubscribePayload,
}

impl VerificationSubscribeEvent {
    #[must_use]
    pub fn new(request_id: &str) -> Self {
        Self {
            event_type: "verification.subscribe",
            payload: VerificationSubscribePayload {
                request_id: request_id.to_string(),
            },
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct VerificationSubscribePayload {
    pub request_id: String,
}

/// Parsed status update from WebSocket.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum StatusUpdate {
    Pending,
    Fulfilled { vp_token: String },
    Denied { reason: String },
    Expired,
    Error { code: String, message: String },
}

impl StatusUpdate {
    #[must_use]
    pub fn from_message(msg: &WsMessage) -> Self {
        match msg {
            WsMessage::VerificationStatus { payload } => match payload.state.as_str() {
                "pending" => Self::Pending,
                "verified" => payload.vp_token.as_ref().map_or_else(
                    || Self::Error {
                        code: "MISSING_VP_TOKEN".into(),
                        message: "Verified but VP token missing".into(),
                    },
                    |token| Self::Fulfilled {
                        vp_token: token.clone(),
                    },
                ),
                "denied" | "failed" => Self::Denied {
                    reason: payload.error.clone().unwrap_or_else(|| "Denied".into()),
                },
                "expired" => Self::Expired,
                unknown => Self::Error {
                    code: "UNKNOWN_STATE".into(),
                    message: format!("Unknown state: {unknown}"),
                },
            },
            WsMessage::Error { payload } => Self::Error {
                code: payload.code.clone(),
                message: payload.message.clone(),
            },
        }
    }

    #[must_use]
    pub const fn is_terminal(&self) -> bool {
        !matches!(self, Self::Pending)
    }
}

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

    #[test]
    fn test_vp_token_as_string() {
        let json = r#"{
            "type": "verification.status",
            "payload": {
                "request_id": "abc123",
                "state": "verified",
                "vp_token": "eyJhbGciOiJFUzI1NiJ9.payload.signature"
            }
        }"#;

        let msg: WsMessage = serde_json::from_str(json).unwrap();
        if let WsMessage::VerificationStatus { payload } = msg {
            assert_eq!(
                payload.vp_token,
                Some("eyJhbGciOiJFUzI1NiJ9.payload.signature".to_string())
            );
        } else {
            panic!("Expected VerificationStatus");
        }
    }

    #[test]
    fn test_vp_token_as_array() {
        let json = r#"{
            "type": "verification.status",
            "payload": {
                "request_id": "abc123",
                "state": "verified",
                "vp_token": ["token1", "token2", "token3"]
            }
        }"#;

        let msg: WsMessage = serde_json::from_str(json).unwrap();
        if let WsMessage::VerificationStatus { payload } = msg {
            assert_eq!(payload.vp_token, Some("token1~token2~token3".to_string()));
        } else {
            panic!("Expected VerificationStatus");
        }
    }

    #[test]
    fn test_vp_token_null() {
        let json = r#"{
            "type": "verification.status",
            "payload": {
                "request_id": "abc123",
                "state": "pending",
                "vp_token": null
            }
        }"#;

        let msg: WsMessage = serde_json::from_str(json).unwrap();
        if let WsMessage::VerificationStatus { payload } = msg {
            assert_eq!(payload.vp_token, None);
        } else {
            panic!("Expected VerificationStatus");
        }
    }

    #[test]
    fn test_vp_token_missing() {
        let json = r#"{
            "type": "verification.status",
            "payload": {
                "request_id": "abc123",
                "state": "pending"
            }
        }"#;

        let msg: WsMessage = serde_json::from_str(json).unwrap();
        if let WsMessage::VerificationStatus { payload } = msg {
            assert_eq!(payload.vp_token, None);
        } else {
            panic!("Expected VerificationStatus");
        }
    }

    #[test]
    fn test_vp_token_empty_array() {
        let json = r#"{
            "type": "verification.status",
            "payload": {
                "request_id": "abc123",
                "state": "pending",
                "vp_token": []
            }
        }"#;

        let msg: WsMessage = serde_json::from_str(json).unwrap();
        if let WsMessage::VerificationStatus { payload } = msg {
            assert_eq!(payload.vp_token, None);
        } else {
            panic!("Expected VerificationStatus");
        }
    }
}
