//! Security Group types

use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// Unique identifier for a SecurityGroup
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SecurityGroupId(Uuid);

impl SecurityGroupId {
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }

    pub fn from_uuid(uuid: Uuid) -> Self {
        Self(uuid)
    }

    pub fn as_uuid(&self) -> &Uuid {
        &self.0
    }
}

impl Default for SecurityGroupId {
    fn default() -> Self {
        Self::new()
    }
}

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

/// Unique identifier for a SecurityGroupRule
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SecurityGroupRuleId(Uuid);

impl SecurityGroupRuleId {
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }

    pub fn from_uuid(uuid: Uuid) -> Self {
        Self(uuid)
    }

    pub fn as_uuid(&self) -> &Uuid {
        &self.0
    }
}

impl Default for SecurityGroupRuleId {
    fn default() -> Self {
        Self::new()
    }
}

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

/// Rule direction
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RuleDirection {
    Ingress,
    Egress,
}

/// IP protocol
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum IpProtocol {
    #[default]
    Any,
    Tcp,
    Udp,
    Icmp,
    Icmpv6,
}


/// Security group rule
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityGroupRule {
    pub id: SecurityGroupRuleId,
    pub security_group_id: SecurityGroupId,
    pub direction: RuleDirection,
    pub protocol: IpProtocol,
    pub port_range_min: Option<u16>,
    pub port_range_max: Option<u16>,
    pub remote_cidr: Option<String>,
    pub remote_group_id: Option<SecurityGroupId>,
    pub description: Option<String>,
    pub created_at: u64,
}

impl SecurityGroupRule {
    pub fn new(
        security_group_id: SecurityGroupId,
        direction: RuleDirection,
        protocol: IpProtocol,
    ) -> Self {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        Self {
            id: SecurityGroupRuleId::new(),
            security_group_id,
            direction,
            protocol,
            port_range_min: None,
            port_range_max: None,
            remote_cidr: None,
            remote_group_id: None,
            description: None,
            created_at: now,
        }
    }

    /// Create an allow-all rule
    pub fn allow_all(security_group_id: SecurityGroupId, direction: RuleDirection) -> Self {
        let mut rule = Self::new(security_group_id, direction, IpProtocol::Any);
        rule.remote_cidr = Some("0.0.0.0/0".to_string());
        rule
    }

    /// Create a TCP port rule
    pub fn tcp_port(
        security_group_id: SecurityGroupId,
        direction: RuleDirection,
        port: u16,
        cidr: impl Into<String>,
    ) -> Self {
        let mut rule = Self::new(security_group_id, direction, IpProtocol::Tcp);
        rule.port_range_min = Some(port);
        rule.port_range_max = Some(port);
        rule.remote_cidr = Some(cidr.into());
        rule
    }
}

/// Security group
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityGroup {
    pub id: SecurityGroupId,
    pub org_id: String,
    pub project_id: String,
    pub name: String,
    pub description: Option<String>,
    pub rules: Vec<SecurityGroupRule>,
    pub created_at: u64,
    pub updated_at: u64,
}

impl SecurityGroup {
    pub fn new(
        name: impl Into<String>,
        org_id: impl Into<String>,
        project_id: impl Into<String>,
    ) -> Self {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        let id = SecurityGroupId::new();

        // Default rules: allow all egress
        let default_egress = SecurityGroupRule::allow_all(id, RuleDirection::Egress);

        Self {
            id,
            org_id: org_id.into(),
            project_id: project_id.into(),
            name: name.into(),
            description: None,
            rules: vec![default_egress],
            created_at: now,
            updated_at: now,
        }
    }

    /// Add a rule to the security group
    pub fn add_rule(&mut self, rule: SecurityGroupRule) {
        self.rules.push(rule);
        self.updated_at = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
    }

    /// Remove a rule by ID
    pub fn remove_rule(&mut self, rule_id: &SecurityGroupRuleId) -> Option<SecurityGroupRule> {
        if let Some(pos) = self.rules.iter().position(|r| &r.id == rule_id) {
            self.updated_at = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs();
            Some(self.rules.remove(pos))
        } else {
            None
        }
    }
}

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

    #[test]
    fn test_security_group_creation() {
        let sg = SecurityGroup::new("default", "org-1", "proj-1");
        assert_eq!(sg.name, "default");
        assert_eq!(sg.rules.len(), 1); // Default egress rule
        assert_eq!(sg.rules[0].direction, RuleDirection::Egress);
    }

    #[test]
    fn test_add_rule() {
        let mut sg = SecurityGroup::new("web", "org-1", "proj-1");
        let ssh_rule = SecurityGroupRule::tcp_port(sg.id, RuleDirection::Ingress, 22, "0.0.0.0/0");
        sg.add_rule(ssh_rule);

        assert_eq!(sg.rules.len(), 2);
    }

    #[test]
    fn test_remove_rule() {
        let mut sg = SecurityGroup::new("test", "org-1", "proj-1");
        let rule_id = sg.rules[0].id;

        let removed = sg.remove_rule(&rule_id);
        assert!(removed.is_some());
        assert!(sg.rules.is_empty());
    }
}
