use anyhow::Result;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusterConfig {
    pub redis_url: String,
    #[serde(default)]
    pub worker: WorkerConfig,
    #[serde(default)]
    pub dispatcher: DispatcherConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerConfig {
    #[serde(default = "default_concurrency")]
    pub concurrency: usize,
    #[serde(default = "default_heartbeat_interval_secs")]
    pub heartbeat_interval_secs: u64,
    #[serde(default)]
    pub capabilities: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DispatcherConfig {
    #[serde(default)]
    pub strategy: DispatchStrategy,
    #[serde(default = "default_task_stream")]
    pub task_stream: String,
    #[serde(default = "default_result_stream")]
    pub result_stream: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum DispatchStrategy {
    #[default]
    LeastLoaded,
    RoundRobin,
    CapabilityMatch,
}

fn default_concurrency() -> usize {
    4
}

fn default_heartbeat_interval_secs() -> u64 {
    10
}

fn default_task_stream() -> String {
    "enact:tasks".to_string()
}

fn default_result_stream() -> String {
    "enact:results".to_string()
}

impl Default for WorkerConfig {
    fn default() -> Self {
        Self {
            concurrency: default_concurrency(),
            heartbeat_interval_secs: default_heartbeat_interval_secs(),
            capabilities: Vec::new(),
        }
    }
}

impl Default for DispatcherConfig {
    fn default() -> Self {
        Self {
            strategy: DispatchStrategy::LeastLoaded,
            task_stream: default_task_stream(),
            result_stream: default_result_stream(),
        }
    }
}

impl Default for ClusterConfig {
    fn default() -> Self {
        Self {
            redis_url: "redis://localhost:6379".to_string(),
            worker: WorkerConfig::default(),
            dispatcher: DispatcherConfig::default(),
        }
    }
}

pub fn load_cluster_config() -> Result<ClusterConfig> {
    match enact_config::resolve_config_file("cluster.yaml", "ENACT_CLUSTER_CONFIG_PATH") {
        Some(path) => {
            let raw = std::fs::read_to_string(&path)?;
            Ok(serde_yaml::from_str(&raw)?)
        }
        None => Ok(ClusterConfig::default()),
    }
}
