//! Cluster state management

use serde::{Deserialize, Serialize};

/// Represents the state of a K3s cluster
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusterState {
    /// The master node IP address
    pub master_ip: String,

    /// Optional private IP for the master node
    pub master_private_ip: Option<String>,

    /// List of worker nodes with their IPs
    pub workers: Vec<WorkerNode>,

    /// K3s version installed
    pub k3s_version: String,

    /// Cluster token
    #[serde(skip_serializing)]
    pub token: String,

    /// Disabled components
    pub disabled_components: Vec<String>,

    /// Timestamp when cluster was created
    pub created_at: Option<std::time::SystemTime>,

    /// Last known cluster status
    pub status: ClusterStatus,
}

/// Represents a worker node in the cluster
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerNode {
    /// Public IP address of the worker
    pub ip: String,

    /// Optional private IP address
    pub private_ip: Option<String>,

    /// Node status
    pub status: NodeStatus,

    /// When the node was added to the cluster
    pub joined_at: Option<std::time::SystemTime>,
}

/// Status of a node
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum NodeStatus {
    /// Node is ready and operational
    Ready,

    /// Node is not ready
    NotReady,

    /// Node status is unknown
    Unknown,

    /// Node is being provisioned
    Provisioning,

    /// Node has failed
    Failed,
}

impl NodeStatus {
    /// Parse node status from kubectl output
    pub fn from_kubectl_output(status: &str) -> Self {
        match status.to_lowercase().as_str() {
            "ready" => NodeStatus::Ready,
            "notready" => NodeStatus::NotReady,
            _ => NodeStatus::Unknown,
        }
    }
}

/// Overall cluster status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ClusterStatus {
    /// Cluster is healthy and operational
    Healthy,

    /// Cluster is degraded (some nodes not ready)
    Degraded,

    /// Cluster is not operational
    Unavailable,

    /// Cluster is being created
    Provisioning,

    /// Cluster state is unknown
    Unknown,
}

impl ClusterState {
    /// Create a new cluster state
    pub fn new(
        master_ip: String,
        master_private_ip: Option<String>,
        k3s_version: String,
        token: String,
        disabled_components: Vec<String>,
    ) -> Self {
        Self {
            master_ip,
            master_private_ip,
            workers: Vec::new(),
            k3s_version,
            token,
            disabled_components,
            created_at: Some(std::time::SystemTime::now()),
            status: ClusterStatus::Provisioning,
        }
    }

    /// Add a worker node to the cluster state
    pub fn add_worker(&mut self, ip: String, private_ip: Option<String>) {
        self.workers.push(WorkerNode {
            ip,
            private_ip,
            status: NodeStatus::Provisioning,
            joined_at: Some(std::time::SystemTime::now()),
        });
    }

    /// Update the status of a specific worker node
    pub fn update_worker_status(&mut self, ip: &str, status: NodeStatus) {
        if let Some(worker) = self.workers.iter_mut().find(|w| w.ip == ip) {
            worker.status = status;
        }
    }

    /// Get the number of ready worker nodes
    pub fn ready_workers_count(&self) -> usize {
        self.workers
            .iter()
            .filter(|w| w.status == NodeStatus::Ready)
            .count()
    }

    /// Check if all nodes are ready
    pub fn all_nodes_ready(&self) -> bool {
        self.workers.iter().all(|w| w.status == NodeStatus::Ready)
    }

    /// Update overall cluster status based on node statuses
    pub fn update_cluster_status(&mut self) {
        if self.all_nodes_ready() && !self.workers.is_empty() {
            self.status = ClusterStatus::Healthy;
        } else if self.ready_workers_count() > 0 {
            self.status = ClusterStatus::Degraded;
        } else {
            self.status = ClusterStatus::Unavailable;
        }
    }
}

/// Node information retrieved from kubectl
#[derive(Debug, Clone)]
pub struct NodeInfo {
    /// Node name
    pub name: String,

    /// Node status
    pub status: NodeStatus,

    /// Node roles (e.g., "control-plane,master")
    pub roles: Vec<String>,

    /// K3s version on this node
    pub version: String,

    /// Internal IP
    pub internal_ip: Option<String>,

    /// External IP
    pub external_ip: Option<String>,
}

impl NodeInfo {
    /// Check if this node is a master/control-plane node
    pub fn is_master(&self) -> bool {
        self.roles
            .iter()
            .any(|r| r.contains("master") || r.contains("control-plane"))
    }

    /// Parse node info from kubectl get nodes output line
    /// Format: NAME STATUS ROLES AGE VERSION
    pub fn parse_from_kubectl_line(line: &str) -> Option<Self> {
        let parts: Vec<&str> = line.split_whitespace().collect();
        if parts.len() < 5 {
            return None;
        }

        Some(NodeInfo {
            name: parts[0].to_string(),
            status: NodeStatus::from_kubectl_output(parts[1]),
            roles: parts[2].split(',').map(|s| s.to_string()).collect(),
            version: parts[4].to_string(),
            internal_ip: None,
            external_ip: None,
        })
    }
}

/// Desired cluster configuration for reconciliation
#[derive(Debug, Clone)]
pub struct DesiredClusterConfig {
    /// Master node IP
    pub master_ip: String,

    /// Master private IP (optional)
    pub master_private_ip: Option<String>,

    /// Desired worker nodes with their IPs
    pub workers: Vec<WorkerConfig>,

    /// K3s version
    pub k3s_version: String,

    /// Cluster token
    pub token: String,

    /// Components to disable
    pub disabled_components: Vec<String>,
}

/// Worker node configuration
#[derive(Debug, Clone)]
pub struct WorkerConfig {
    /// Public IP of the worker
    pub ip: String,

    /// Private IP (optional)
    pub private_ip: Option<String>,
}

impl WorkerConfig {
    /// Create a new worker config with just public IP
    pub fn new(ip: String) -> Self {
        Self {
            ip,
            private_ip: None,
        }
    }

    /// Create a new worker config with both public and private IPs
    pub fn with_private_ip(ip: String, private_ip: String) -> Self {
        Self {
            ip,
            private_ip: Some(private_ip),
        }
    }
}

/// Reconciliation plan - what needs to be done to reach desired state
#[derive(Debug, Clone)]
pub struct ReconciliationPlan {
    /// Workers that need to be added
    pub workers_to_add: Vec<WorkerConfig>,

    /// Workers that need to be removed (by IP)
    pub workers_to_remove: Vec<String>,

    /// Whether master needs to be installed
    pub install_master: bool,
}

impl ReconciliationPlan {
    /// Check if there are any changes needed
    pub fn has_changes(&self) -> bool {
        self.install_master || !self.workers_to_add.is_empty() || !self.workers_to_remove.is_empty()
    }

    /// Get a summary of the plan
    pub fn summary(&self) -> String {
        let mut parts = Vec::new();

        if self.install_master {
            parts.push("Install master".to_string());
        }

        if !self.workers_to_add.is_empty() {
            parts.push(format!("Add {} worker(s)", self.workers_to_add.len()));
        }

        if !self.workers_to_remove.is_empty() {
            parts.push(format!("Remove {} worker(s)", self.workers_to_remove.len()));
        }

        if parts.is_empty() {
            "No changes needed".to_string()
        } else {
            parts.join(", ")
        }
    }
}
