//! Request status type definition.

use serde::{Deserialize, Serialize};

use super::RequestId;

/// Credential request lifecycle states.
///
/// # State Machine
///
/// ```text
/// ┌─────────┐
/// │ Pending │◄──── Initial state after request submission
/// └────┬────┘
///      │
///      ├──► Fulfilled ──────► Terminal state (success)
///      │
///      ├──► Denied ──────────► Terminal state (user declined)
///      │
///      ├──► Expired ─────────► Terminal state (request TTL exceeded)
///      │
///      ├──► NotFound ────────► Terminal state (request ID invalid/deleted)
///      │
///      └──► Timeout ─────────► Terminal state (polling timeout)
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "status", rename_all = "lowercase")]
#[non_exhaustive]
pub enum RequestStatus {
    /// Request is pending user action
    Pending {
        /// Request identifier
        request_id: RequestId,
        /// Creation timestamp
        created_at: String,
    },

    /// User completed the credential presentation
    Fulfilled {
        /// Request identifier
        request_id: RequestId,
        /// SD-JWT VC presentation
        presentation: String,
        /// Fulfillment timestamp
        fulfilled_at: String,
    },

    /// User declined the request
    Denied {
        /// Request identifier
        request_id: RequestId,
        /// Denial reason
        reason: String,
        /// Denial timestamp
        denied_at: String,
    },

    /// Request expired (IDP timeout, typically 5 minutes)
    Expired {
        /// Request identifier
        request_id: RequestId,
        /// Expiration timestamp
        expired_at: String,
    },

    /// Request not found (invalid request ID or already deleted)
    NotFound {
        /// Request identifier that was not found
        request_id: RequestId,
        /// Error message from IDP
        message: String,
    },

    /// SDK polling timeout reached
    Timeout {
        /// Request identifier
        request_id: RequestId,
        /// Elapsed seconds before timeout
        elapsed_seconds: u64,
    },
}

impl RequestStatus {
    /// Check if this status is terminal (no further changes possible).
    #[must_use]
    pub const fn is_terminal(&self) -> bool {
        !matches!(self, Self::Pending { .. })
    }

    /// Check if this status indicates success.
    #[must_use]
    pub const fn is_fulfilled(&self) -> bool {
        matches!(self, Self::Fulfilled { .. })
    }

    /// Get the request ID.
    #[must_use]
    pub const fn request_id(&self) -> &RequestId {
        match self {
            Self::Pending { request_id, .. }
            | Self::Fulfilled { request_id, .. }
            | Self::Denied { request_id, .. }
            | Self::Expired { request_id, .. }
            | Self::NotFound { request_id, .. }
            | Self::Timeout { request_id, .. } => request_id,
        }
    }

    /// Get the presentation if fulfilled.
    #[must_use]
    pub fn presentation(&self) -> Option<&str> {
        match self {
            Self::Fulfilled { presentation, .. } => Some(presentation),
            _ => None,
        }
    }
}

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

    #[test]
    fn test_pending_not_terminal() {
        let status = RequestStatus::Pending {
            request_id: RequestId::new("req_123"),
            created_at: "2026-01-16T12:00:00Z".into(),
        };
        assert!(!status.is_terminal());
        assert!(!status.is_fulfilled());
    }

    #[test]
    fn test_fulfilled_is_terminal() {
        let status = RequestStatus::Fulfilled {
            request_id: RequestId::new("req_123"),
            presentation: "sdjwt".into(),
            fulfilled_at: "2026-01-16T12:05:00Z".into(),
        };
        assert!(status.is_terminal());
        assert!(status.is_fulfilled());
        assert_eq!(status.presentation(), Some("sdjwt"));
    }

    #[test]
    fn test_denied_is_terminal() {
        let status = RequestStatus::Denied {
            request_id: RequestId::new("req_123"),
            reason: "User declined".into(),
            denied_at: "2026-01-16T12:05:00Z".into(),
        };
        assert!(status.is_terminal());
        assert!(!status.is_fulfilled());
    }

    #[test]
    fn test_request_id_extraction() {
        let status = RequestStatus::Pending {
            request_id: RequestId::new("req_abc"),
            created_at: "2026-01-16T12:00:00Z".into(),
        };
        assert_eq!(status.request_id().as_str(), "req_abc");
    }

    #[test]
    fn test_serialize_pending() {
        let status = RequestStatus::Pending {
            request_id: RequestId::new("req_123"),
            created_at: "2026-01-16T12:00:00Z".into(),
        };

        let json = serde_json::to_value(&status).unwrap();
        assert_eq!(json["status"], "pending");
        assert_eq!(json["request_id"], "req_123");
    }

    #[test]
    fn test_deserialize_fulfilled() {
        let json = r#"{
            "status": "fulfilled",
            "request_id": "req_123",
            "presentation": "eyJhbGciOiJFUzI1NiJ9...",
            "fulfilled_at": "2026-01-16T12:05:00Z"
        }"#;

        let status: RequestStatus = serde_json::from_str(json).unwrap();
        assert!(status.is_fulfilled());
        assert_eq!(status.request_id().as_str(), "req_123");
    }

    #[test]
    fn test_notfound_is_terminal() {
        let status = RequestStatus::NotFound {
            request_id: RequestId::new("req_123"),
            message: "Verification request not found".into(),
        };
        assert!(status.is_terminal());
        assert!(!status.is_fulfilled());
        assert_eq!(status.request_id().as_str(), "req_123");
    }

    #[test]
    fn test_serialize_notfound() {
        let status = RequestStatus::NotFound {
            request_id: RequestId::new("req_404"),
            message: "Request deleted or expired".into(),
        };

        let json = serde_json::to_value(&status).unwrap();
        assert_eq!(json["status"], "notfound");
        assert_eq!(json["request_id"], "req_404");
        assert_eq!(json["message"], "Request deleted or expired");
    }

    #[test]
    fn test_deserialize_notfound() {
        let json = r#"{
            "status": "notfound",
            "request_id": "req_404",
            "message": "Verification request not found"
        }"#;

        let status: RequestStatus = serde_json::from_str(json).unwrap();
        assert!(status.is_terminal());
        assert!(!status.is_fulfilled());
        assert_eq!(status.request_id().as_str(), "req_404");
    }
}
