//! Kubernetes pod listing for the Pods cockpit.
//!
//! Queries the in-cluster Kubernetes API (via `kube` crate) to list pods in a
//! given namespace, converting them into proto `PodInfo` messages. The handler
//! is called periodically by the `subscribe_cluster_pods` streaming RPC.

use anyhow::Result;
use k8s_openapi::api::core::v1::Pod;
use kube::{Api, Client};

/// Fetch all pods in a namespace and convert to proto `PodInfo`.
pub async fn list_pods(namespace: &str) -> Result<Vec<crate::monitoring::PodInfo>> {
    let client = Client::try_default().await?;
    let pods: Api<Pod> = Api::namespaced(client, namespace);
    let pod_list = pods.list(&Default::default()).await?;

    let mut infos = Vec::with_capacity(pod_list.items.len());
    for pod in pod_list.items {
        let meta = &pod.metadata;
        let spec = pod.spec.as_ref();
        let status = pod.status.as_ref();

        let name = meta.name.clone().unwrap_or_default();
        let labels = meta.labels.as_ref();
        let service = labels
            .and_then(|l| l.get("app.kubernetes.io/name"))
            .cloned()
            .unwrap_or_default();

        let phase = status
            .and_then(|s| s.phase.clone())
            .unwrap_or_else(|| "Unknown".into());

        let ready = status
            .and_then(|s| s.conditions.as_ref())
            .map(|conds| {
                conds
                    .iter()
                    .any(|c| c.type_ == "Ready" && c.status == "True")
            })
            .unwrap_or(false);

        let restarts: i32 = status
            .and_then(|s| s.container_statuses.as_ref())
            .map(|cs| cs.iter().map(|c| c.restart_count).sum())
            .unwrap_or(0);

        let node = spec
            .and_then(|s| s.node_name.clone())
            .unwrap_or_default();

        let age = meta
            .creation_timestamp
            .as_ref()
            .map(|t| format_age(&t.0))
            .unwrap_or_else(|| "?".into());

        // Try to get version from FOXHUNT_BUILD_VERSION env var in first container
        let version = spec
            .and_then(|s| s.containers.first())
            .and_then(|c| c.env.as_ref())
            .and_then(|envs| {
                envs.iter()
                    .find(|e| e.name == "FOXHUNT_BUILD_VERSION")
                    .and_then(|e| e.value.clone())
            })
            .unwrap_or_default();

        infos.push(crate::monitoring::PodInfo {
            name,
            service,
            namespace: namespace.into(),
            status: phase,
            restarts,
            age,
            node: short_node_name(&node),
            ready,
            version,
        });
    }

    infos.sort_by(|a, b| (&a.service, &a.name).cmp(&(&b.service, &b.name)));
    Ok(infos)
}

/// Shorten Scaleway node names: "scw-foxhunt-pool-default-abc123" -> "pool-default-abc123"
fn short_node_name(full: &str) -> String {
    full.strip_prefix("scw-foxhunt-")
        .unwrap_or(full)
        .split('-')
        .take(3)
        .collect::<Vec<_>>()
        .join("-")
        .chars()
        .take(20)
        .collect()
}

/// Format a duration since `created` as a human-readable age string.
///
/// k8s-openapi 0.27+ uses `jiff::Timestamp` for `creation_timestamp`.
fn format_age(created: &k8s_openapi::jiff::Timestamp) -> String {
    let now = k8s_openapi::jiff::Timestamp::now();
    let secs = now.as_second() - created.as_second();
    if secs < 60 {
        format!("{secs}s")
    } else if secs < 3600 {
        format!("{}m", secs / 60)
    } else if secs < 86400 {
        format!("{}h{}m", secs / 3600, (secs % 3600) / 60)
    } else {
        format!("{}d{}h", secs / 86400, (secs % 86400) / 3600)
    }
}
