//! Service IP Pool types for k8shost IPAM integration

use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use uuid::Uuid;

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

impl ServiceIPPoolId {
    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 ServiceIPPoolId {
    fn default() -> Self {
        Self::new()
    }
}

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

/// Service IP Pool type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ServiceIPPoolType {
    /// ClusterIP pool for internal service IPs
    ClusterIp,
    /// LoadBalancer pool for external VIPs
    LoadBalancer,
    /// NodePort range (for future use)
    NodePort,
}

impl Default for ServiceIPPoolType {
    fn default() -> Self {
        Self::ClusterIp
    }
}

/// Service IP Pool status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ServiceIPPoolStatus {
    /// Pool is being created
    Provisioning,
    /// Pool is ready for allocations
    Active,
    /// Pool is being updated
    Updating,
    /// Pool is being deleted
    Deleting,
    /// Pool has errors
    Error,
}

impl Default for ServiceIPPoolStatus {
    fn default() -> Self {
        Self::Provisioning
    }
}

/// Service IP Pool for k8shost Service allocation
///
/// Provides a dedicated IP range for Kubernetes Service IPs (ClusterIP, LoadBalancer)
/// with tenant isolation and allocation tracking.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceIPPool {
    /// Unique identifier
    pub id: ServiceIPPoolId,
    /// Organization ID (tenant isolation)
    pub org_id: String,
    /// Project ID (tenant isolation)
    pub project_id: String,
    /// Human-readable name
    pub name: String,
    /// Description
    pub description: Option<String>,
    /// CIDR block for this pool (e.g., "10.96.0.0/16")
    pub cidr_block: String,
    /// Pool type (ClusterIP, LoadBalancer, NodePort)
    pub pool_type: ServiceIPPoolType,
    /// Set of currently allocated IP addresses
    pub allocated_ips: HashSet<String>,
    /// Pool status
    pub status: ServiceIPPoolStatus,
    /// Creation timestamp (Unix epoch)
    pub created_at: u64,
    /// Last update timestamp (Unix epoch)
    pub updated_at: u64,
}

impl ServiceIPPool {
    /// Create a new Service IP Pool
    pub fn new(
        name: impl Into<String>,
        org_id: impl Into<String>,
        project_id: impl Into<String>,
        cidr_block: impl Into<String>,
        pool_type: ServiceIPPoolType,
    ) -> Self {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        Self {
            id: ServiceIPPoolId::new(),
            org_id: org_id.into(),
            project_id: project_id.into(),
            name: name.into(),
            description: None,
            cidr_block: cidr_block.into(),
            pool_type,
            allocated_ips: HashSet::new(),
            status: ServiceIPPoolStatus::Active,
            created_at: now,
            updated_at: now,
        }
    }

    /// Check if an IP address is allocated
    pub fn is_allocated(&self, ip: &str) -> bool {
        self.allocated_ips.contains(ip)
    }

    /// Get number of allocated IPs
    pub fn allocated_count(&self) -> usize {
        self.allocated_ips.len()
    }

    /// Mark IP as allocated
    pub fn allocate_ip(&mut self, ip: String) {
        self.allocated_ips.insert(ip);
        self.updated_at = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
    }

    /// Release an allocated IP
    pub fn release_ip(&mut self, ip: &str) -> bool {
        let removed = self.allocated_ips.remove(ip);
        if removed {
            self.updated_at = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs();
        }
        removed
    }
}

/// IP Allocation record
///
/// Tracks which resource (k8s Service, VM Port, etc.) owns an allocated IP address.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IPAllocation {
    /// Allocated IP address
    pub ip_address: String,
    /// Pool from which IP was allocated
    pub pool_id: ServiceIPPoolId,
    /// Organization ID
    pub org_id: String,
    /// Project ID
    pub project_id: String,
    /// Resource type (e.g., "k8s-service", "vm-port")
    pub resource_type: String,
    /// Resource ID (e.g., Service UID, Port ID)
    pub resource_id: String,
    /// Allocation timestamp (Unix epoch)
    pub allocated_at: u64,
}

impl IPAllocation {
    /// Create a new IP allocation record
    pub fn new(
        ip_address: impl Into<String>,
        pool_id: ServiceIPPoolId,
        org_id: impl Into<String>,
        project_id: impl Into<String>,
        resource_type: impl Into<String>,
        resource_id: impl Into<String>,
    ) -> Self {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        Self {
            ip_address: ip_address.into(),
            pool_id,
            org_id: org_id.into(),
            project_id: project_id.into(),
            resource_type: resource_type.into(),
            resource_id: resource_id.into(),
            allocated_at: now,
        }
    }
}

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

    #[test]
    fn test_service_ip_pool_allocation() {
        let mut pool = ServiceIPPool::new(
            "test-pool",
            "org-123",
            "proj-456",
            "10.96.0.0/16",
            ServiceIPPoolType::ClusterIp,
        );

        assert_eq!(pool.allocated_count(), 0);
        assert!(!pool.is_allocated("10.96.0.1"));

        pool.allocate_ip("10.96.0.1".to_string());
        assert_eq!(pool.allocated_count(), 1);
        assert!(pool.is_allocated("10.96.0.1"));

        assert!(pool.release_ip("10.96.0.1"));
        assert_eq!(pool.allocated_count(), 0);
        assert!(!pool.is_allocated("10.96.0.1"));

        // Releasing again should return false
        assert!(!pool.release_ip("10.96.0.1"));
    }

    #[test]
    fn test_ip_allocation_record() {
        let pool_id = ServiceIPPoolId::new();
        let alloc = IPAllocation::new(
            "10.96.0.1",
            pool_id,
            "org-123",
            "proj-456",
            "k8s-service",
            "service-uid-789",
        );

        assert_eq!(alloc.ip_address, "10.96.0.1");
        assert_eq!(alloc.pool_id, pool_id);
        assert_eq!(alloc.resource_type, "k8s-service");
        assert_eq!(alloc.resource_id, "service-uid-789");
    }
}
