use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::env;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::sync::{Mutex, OnceLock};
use uuid::Uuid;

static RUNTIME_DAEMON_IDS: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();
static PROCESS_DAEMON_ID_OVERRIDE: OnceLock<String> = OnceLock::new();

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DaemonId(String);

impl DaemonId {
    pub fn new(value: impl Into<String>) -> Result<Self, crate::ControlPlaneError> {
        let trimmed = value.into().trim().to_string();
        if trimmed.is_empty() {
            return Err(crate::ControlPlaneError::InvalidInput(
                "daemon id must not be empty".to_string(),
            ));
        }
        Ok(Self(trimmed))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

pub fn normalize_daemon_id(value: Option<&str>) -> Option<String> {
    value
        .map(str::trim)
        .filter(|raw| !raw.is_empty())
        .map(ToString::to_string)
}

pub fn first_daemon_env_value() -> Option<String> {
    const DAEMON_ID_ENV_KEYS: [&str; 4] = [
        "ATHENA_DAEMON_ID",
        "ATHENA_WORKER_ID",
        "HOSTNAME",
        "COMPUTERNAME",
    ];

    DAEMON_ID_ENV_KEYS
        .iter()
        .filter_map(|key| env::var(key).ok())
        .find_map(|raw| normalize_daemon_id(Some(&raw)))
}

pub fn install_process_daemon_id(
    value: impl Into<String>,
) -> Result<DaemonId, crate::ControlPlaneError> {
    let daemon_id = DaemonId::new(value)?;
    let stored = PROCESS_DAEMON_ID_OVERRIDE
        .get_or_init(|| daemon_id.as_str().to_string())
        .clone();
    DaemonId::new(stored)
}

pub fn process_daemon_id_override() -> Option<String> {
    PROCESS_DAEMON_ID_OVERRIDE.get().cloned()
}

pub fn resolve_runtime_host() -> String {
    ["HOSTNAME", "COMPUTERNAME"]
        .iter()
        .filter_map(|key| env::var(key).ok())
        .find_map(|raw| normalize_daemon_id(Some(&raw)))
        .unwrap_or_else(|| "unknown-host".to_string())
}

pub fn resolve_runtime_daemon_id(
    runtime_kind: &str,
    override_value: Option<&str>,
) -> Result<DaemonId, crate::ControlPlaneError> {
    let cache_key = runtime_kind.trim().to_string();

    let resolved = if let Some(resolved) = normalize_daemon_id(override_value)
        .or_else(process_daemon_id_override)
        .or_else(first_daemon_env_value)
    {
        DaemonId::new(resolved)?
    } else if !cache_key.is_empty() {
        if let Some(cached) = RUNTIME_DAEMON_IDS
            .get_or_init(|| Mutex::new(HashMap::new()))
            .lock()
            .expect("runtime daemon id cache lock poisoned")
            .get(&cache_key)
            .cloned()
        {
            return DaemonId::new(cached);
        }
        let normalized_runtime_kind = runtime_kind.trim().replace('_', "-");
        let runtime_kind = if normalized_runtime_kind.is_empty() {
            "athena-runtime".to_string()
        } else {
            normalized_runtime_kind
        };
        let host = resolve_runtime_host();
        DaemonId::new(format!("{runtime_kind}-{host}-{}", Uuid::new_v4()))?
    } else {
        let host = resolve_runtime_host();
        DaemonId::new(format!("athena-runtime-{host}-{}", Uuid::new_v4()))?
    };

    if !cache_key.is_empty() {
        RUNTIME_DAEMON_IDS
            .get_or_init(|| Mutex::new(HashMap::new()))
            .lock()
            .expect("runtime daemon id cache lock poisoned")
            .insert(cache_key, resolved.as_str().to_string());
    }

    Ok(resolved)
}

impl Display for DaemonId {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.write_str(self.as_str())
    }
}
