//! Resource types for IAM
//!
//! Resources represent objects that can be protected by IAM policies.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;

/// A resource that can be protected by IAM policies
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Resource {
    /// Resource kind (e.g., "instance", "volume", "bucket")
    pub kind: String,

    /// Resource identifier
    pub id: String,

    /// Organization ID that owns this resource
    pub org_id: String,

    /// Project ID that owns this resource
    pub project_id: String,

    /// Optional owner principal ID
    pub owner_id: Option<String>,

    /// Node ID where the resource is located (for node-scoped resources)
    pub node_id: Option<String>,

    /// Region/zone information
    pub region: Option<String>,

    /// Resource tags
    pub tags: HashMap<String, String>,
}

impl Resource {
    /// Create a new resource
    pub fn new(
        kind: impl Into<String>,
        id: impl Into<String>,
        org_id: impl Into<String>,
        project_id: impl Into<String>,
    ) -> Self {
        Self {
            kind: kind.into(),
            id: id.into(),
            org_id: org_id.into(),
            project_id: project_id.into(),
            owner_id: None,
            node_id: None,
            region: None,
            tags: HashMap::new(),
        }
    }

    /// Set the owner
    pub fn with_owner(mut self, owner_id: impl Into<String>) -> Self {
        self.owner_id = Some(owner_id.into());
        self
    }

    /// Set the node ID
    pub fn with_node(mut self, node_id: impl Into<String>) -> Self {
        self.node_id = Some(node_id.into());
        self
    }

    /// Set the region
    pub fn with_region(mut self, region: impl Into<String>) -> Self {
        self.region = Some(region.into());
        self
    }

    /// Add a tag
    pub fn with_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.tags.insert(key.into(), value.into());
        self
    }

    /// Convert to resource path (e.g., "org/my-org/project/my-project/instance/vm-123")
    pub fn to_path(&self) -> String {
        format!(
            "org/{}/project/{}/{}/{}",
            self.org_id, self.project_id, self.kind, self.id
        )
    }

    /// Get a property value for condition evaluation
    pub fn get_property(&self, key: &str) -> Option<String> {
        match key {
            "kind" => Some(self.kind.clone()),
            "id" => Some(self.id.clone()),
            "org_id" | "org" => Some(self.org_id.clone()),
            "project_id" | "project" => Some(self.project_id.clone()),
            "owner" | "owner_id" => self.owner_id.clone(),
            "node" | "node_id" => self.node_id.clone(),
            "region" => self.region.clone(),
            key if key.starts_with("tags.") => {
                let tag_key = &key[5..];
                self.tags.get(tag_key).cloned()
            }
            _ => None,
        }
    }
}

impl fmt::Display for Resource {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.to_path())
    }
}

/// Reference to a resource (kind:org:project:id or pattern)
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ResourceRef {
    pub kind: String,
    pub org_id: String,
    pub project_id: String,
    pub id: String,
}

impl ResourceRef {
    /// Create a new resource reference
    pub fn new(
        kind: impl Into<String>,
        org_id: impl Into<String>,
        project_id: impl Into<String>,
        id: impl Into<String>,
    ) -> Self {
        Self {
            kind: kind.into(),
            org_id: org_id.into(),
            project_id: project_id.into(),
            id: id.into(),
        }
    }

    /// Create from a Resource
    pub fn from_resource(resource: &Resource) -> Self {
        Self {
            kind: resource.kind.clone(),
            org_id: resource.org_id.clone(),
            project_id: resource.project_id.clone(),
            id: resource.id.clone(),
        }
    }
}

impl fmt::Display for ResourceRef {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "org/{}/project/{}/{}/{}",
            self.org_id, self.project_id, self.kind, self.id
        )
    }
}

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

    #[test]
    fn test_resource_path() {
        let resource = Resource::new("instance", "vm-123", "my-org", "my-project");
        assert_eq!(
            resource.to_path(),
            "org/my-org/project/my-project/instance/vm-123"
        );
    }

    #[test]
    fn test_resource_property() {
        let resource = Resource::new("instance", "vm-123", "my-org", "my-project")
            .with_owner("user:alice")
            .with_tag("env", "prod");

        assert_eq!(resource.get_property("kind"), Some("instance".into()));
        assert_eq!(resource.get_property("org_id"), Some("my-org".into()));
        assert_eq!(
            resource.get_property("project_id"),
            Some("my-project".into())
        );
        assert_eq!(resource.get_property("owner"), Some("user:alice".into()));
        assert_eq!(resource.get_property("tags.env"), Some("prod".into()));
        assert_eq!(resource.get_property("tags.unknown"), None);
    }

    #[test]
    fn test_resource_serialization() {
        let resource =
            Resource::new("instance", "vm-123", "my-org", "my-project").with_owner("user:alice");
        let json = serde_json::to_string(&resource).unwrap();
        let parsed: Resource = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.kind, "instance");
        assert_eq!(parsed.org_id, "my-org");
        assert_eq!(parsed.owner_id, Some("user:alice".into()));
    }

    #[test]
    fn test_resource_ref() {
        let resource = Resource::new("instance", "vm-123", "my-org", "my-project");
        let resource_ref = ResourceRef::from_resource(&resource);
        assert_eq!(resource_ref.kind, "instance");
        assert_eq!(resource_ref.org_id, "my-org");
        assert_eq!(resource_ref.project_id, "my-project");
        assert_eq!(resource_ref.id, "vm-123");
        assert_eq!(
            resource_ref.to_string(),
            "org/my-org/project/my-project/instance/vm-123"
        );
    }
}
