//! Error types for IAM

use thiserror::Error;

/// Result type alias using IAM's Error
pub type Result<T> = std::result::Result<T, Error>;

/// Main error type for IAM operations
#[derive(Error, Debug)]
pub enum Error {
    /// IAM-specific errors
    #[error("IAM error: {0}")]
    Iam(#[from] IamError),

    /// Storage layer errors
    #[error("Storage error: {0}")]
    Storage(#[from] StorageError),

    /// Serialization errors
    #[error("Serialization error: {0}")]
    Serialization(String),

    /// Configuration errors
    #[error("Configuration error: {0}")]
    Config(String),

    /// Generic internal error
    #[error("Internal error: {0}")]
    Internal(String),
}

/// IAM-specific errors
#[derive(Error, Debug)]
pub enum IamError {
    /// Authentication failed
    #[error("Authentication failed: {0}")]
    AuthnFailed(String),

    /// Authorization denied
    #[error("Authorization denied: {reason}")]
    AuthzDenied { reason: String },

    /// Principal not found
    #[error("Principal not found: {0}")]
    PrincipalNotFound(String),

    /// Role not found
    #[error("Role not found: {0}")]
    RoleNotFound(String),

    /// Organization not found
    #[error("Organization not found: {0}")]
    OrganizationNotFound(String),

    /// Project not found
    #[error("Project not found: {0}")]
    ProjectNotFound(String),

    /// Binding not found
    #[error("Binding not found: {0}")]
    BindingNotFound(String),

    /// Invalid token
    #[error("Invalid token: {0}")]
    InvalidToken(String),

    /// Token expired
    #[error("Token expired")]
    TokenExpired,

    /// Invalid scope
    #[error("Invalid scope: {0}")]
    InvalidScope(String),

    /// Invalid condition
    #[error("Invalid condition: {0}")]
    InvalidCondition(String),

    /// Invalid action format
    #[error("Invalid action format: {0}")]
    InvalidAction(String),

    /// Invalid resource pattern
    #[error("Invalid resource pattern: {0}")]
    InvalidResourcePattern(String),

    /// Principal already exists
    #[error("Principal already exists: {0}")]
    PrincipalAlreadyExists(String),

    /// Role already exists
    #[error("Role already exists: {0}")]
    RoleAlreadyExists(String),

    /// Organization already exists
    #[error("Organization already exists: {0}")]
    OrganizationAlreadyExists(String),

    /// Project already exists
    #[error("Project already exists: {0}")]
    ProjectAlreadyExists(String),

    /// Cannot modify builtin role
    #[error("Cannot modify builtin role: {0}")]
    CannotModifyBuiltinRole(String),

    /// Binding already exists
    #[error("Binding already exists: {0}")]
    BindingAlreadyExists(String),

    /// Insufficient permissions
    #[error("Insufficient permissions to perform this operation")]
    InsufficientPermissions,
}

/// Storage layer errors
#[derive(Error, Debug)]
pub enum StorageError {
    /// Key not found
    #[error("Key not found: {0}")]
    KeyNotFound(String),

    /// IO error
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    /// Backend error
    #[error("Backend error: {0}")]
    Backend(String),

    /// Serialization error
    #[error("Serialization error: {0}")]
    Serialization(String),

    /// Connection error
    #[error("Connection error: {0}")]
    Connection(String),

    /// CAS conflict
    #[error("CAS conflict: expected version {expected}, actual version {actual}")]
    CasConflict { expected: u64, actual: u64 },

    /// Timeout
    #[error("Storage operation timeout")]
    Timeout,
}

impl From<serde_json::Error> for Error {
    fn from(err: serde_json::Error) -> Self {
        Error::Serialization(err.to_string())
    }
}

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

    #[test]
    fn test_error_display() {
        let err = Error::Iam(IamError::AuthnFailed("invalid credentials".into()));
        assert!(err.to_string().contains("Authentication failed"));

        let err = Error::Iam(IamError::AuthzDenied {
            reason: "no matching policy".into(),
        });
        assert!(err.to_string().contains("Authorization denied"));
    }

    #[test]
    fn test_error_conversion() {
        let iam_err = IamError::PrincipalNotFound("user:alice".into());
        let err: Error = iam_err.into();
        assert!(matches!(err, Error::Iam(_)));
    }
}
