pub mod k8s;
mod manager;
use std::str::FromStr;

pub use manager::*;
use serde::{Deserialize, Serialize};

use crate::data::Id;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] // Assuming serde for Scores
pub struct TenantConfig {
    /// This will be used as the primary unique identifier for management operations and will never
    /// change for the entire lifetime of the tenant
    pub id: Id,

    /// A human-readable name for the tenant (e.g., "client-alpha", "project-phoenix").
    pub name: String,

    /// Desired resource allocations and limits for the tenant.
    pub resource_limits: ResourceLimits,

    /// High-level network isolation policies for the tenant.
    pub network_policy: TenantNetworkPolicy,
}

impl Default for TenantConfig {
    fn default() -> Self {
        let id = Id::default();
        Self {
            name: format!("tenant_{id}"),
            id,
            resource_limits: ResourceLimits::default(),
            network_policy: TenantNetworkPolicy {
                default_inter_tenant_ingress: InterTenantIngressPolicy::DenyAll,
                default_internet_egress: InternetEgressPolicy::AllowAll,
                additional_allowed_cidr_ingress: vec![],
                additional_allowed_cidr_egress: vec![],
            },
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResourceLimits {
    /// Requested/guaranteed CPU cores (e.g., 2.0).
    pub cpu_request_cores: f32,
    /// Maximum CPU cores the tenant can burst to (e.g., 4.0).
    pub cpu_limit_cores: f32,

    /// Requested/guaranteed memory in Gigabytes (e.g., 8.0).
    pub memory_request_gb: f32,
    /// Maximum memory in Gigabytes tenant can burst to (e.g., 16.0).
    pub memory_limit_gb: f32,

    /// Total persistent storage allocation in Gigabytes across all volumes.
    pub storage_total_gb: f32,
}

impl Default for ResourceLimits {
    fn default() -> Self {
        Self {
            cpu_request_cores: 4.0,
            cpu_limit_cores: 4.0,
            memory_request_gb: 4.0,
            memory_limit_gb: 4.0,
            storage_total_gb: 20.0,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TenantNetworkPolicy {
    /// Policy for ingress traffic originating from other tenants within the same Harmony-managed environment.
    pub default_inter_tenant_ingress: InterTenantIngressPolicy,

    /// Policy for egress traffic destined for the public internet.
    pub default_internet_egress: InternetEgressPolicy,

    pub additional_allowed_cidr_ingress: Vec<(Vec<cidr::Ipv4Cidr>, Option<PortSpec>)>,
    pub additional_allowed_cidr_egress: Vec<(Vec<cidr::Ipv4Cidr>, Option<PortSpec>)>,
}

impl Default for TenantNetworkPolicy {
    fn default() -> Self {
        TenantNetworkPolicy {
            default_inter_tenant_ingress: InterTenantIngressPolicy::DenyAll,
            default_internet_egress: InternetEgressPolicy::DenyAll,
            additional_allowed_cidr_ingress: vec![],
            additional_allowed_cidr_egress: vec![],
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum InterTenantIngressPolicy {
    /// Deny all traffic from other tenants by default.
    DenyAll,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum InternetEgressPolicy {
    /// Allow all outbound traffic to the internet.
    AllowAll,
    /// Deny all outbound traffic to the internet by default.
    DenyAll,
}

/// Represents a port specification that can be either a single port, a comma-separated list of ports,
/// or a range separated by a dash.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PortSpec {
    /// The actual representation of the ports as strings for serialization/deserialization purposes.
    pub data: PortSpecData,
}

impl PortSpec {
    /// TODO write short rust doc that shows what types of input are supported
    fn parse_from_str(spec: &str) -> Result<PortSpec, String> {
        // Check for single port
        if let Ok(port) = spec.parse::<u16>() {
            let spec = PortSpecData::SinglePort(port);
            return Ok(Self { data: spec });
        }

        if let Some(range) = spec.find('-') {
            let start_str = &spec[..range];
            let end_str = &spec[(range + 1)..];

            if let (Ok(start), Ok(end)) = (start_str.parse::<u16>(), end_str.parse::<u16>()) {
                let spec = PortSpecData::PortRange(start, end);
                return Ok(Self { data: spec });
            }
        }

        let ports: Vec<&str> = spec.split(',').collect();
        if !ports.is_empty() && ports.iter().all(|p| p.parse::<u16>().is_ok()) {
            let maybe_ports = ports.iter().try_fold(vec![], |mut list, &p| {
                if let Ok(p) = p.parse::<u16>() {
                    list.push(p);
                    return Ok(list);
                }
                Err(())
            });

            if let Ok(ports) = maybe_ports {
                let spec = PortSpecData::ListOfPorts(ports);
                return Ok(Self { data: spec });
            }
        }

        Err(format!("Invalid port spec format {spec}"))
    }
}

impl FromStr for PortSpec {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Self::parse_from_str(s)
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PortSpecData {
    SinglePort(u16),
    PortRange(u16, u16),
    ListOfPorts(Vec<u16>),
}

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

    #[test]
    fn test_single_port() {
        let port_spec = "2144".parse::<PortSpec>().unwrap();
        match port_spec.data {
            PortSpecData::SinglePort(port) => assert_eq!(port, 2144),
            _ => panic!("Expected SinglePort"),
        }
    }

    #[test]
    fn test_port_range() {
        let port_spec = "80-90".parse::<PortSpec>().unwrap();
        match port_spec.data {
            PortSpecData::PortRange(start, end) => {
                assert_eq!(start, 80);
                assert_eq!(end, 90);
            }
            _ => panic!("Expected PortRange"),
        }
    }

    #[test]
    fn test_list_of_ports() {
        let port_spec = "2144,3424".parse::<PortSpec>().unwrap();
        match port_spec.data {
            PortSpecData::ListOfPorts(ports) => {
                assert_eq!(ports[0], 2144);
                assert_eq!(ports[1], 3424);
            }
            _ => panic!("Expected ListOfPorts"),
        }
    }

    #[test]
    fn test_invalid_port_spec() {
        let result = "invalid".parse::<PortSpec>();
        assert!(result.is_err());
    }

    #[test]
    fn test_empty_input() {
        let result = "".parse::<PortSpec>();
        assert!(result.is_err());
    }

    #[test]
    fn test_only_coma() {
        let result = ",".parse::<PortSpec>();
        assert!(result.is_err());
    }
}
