//! Background task for periodic metrics updates.
//!
//! This module implements a separate background task that periodically
//! updates node metrics in etcd at a configurable interval.

use std::time::{Duration, SystemTime, UNIX_EPOCH};

use etcd_client::{Client, PutOptions};
use tokio::sync::watch;
use tokio::task::JoinHandle;
use tracing::{debug, error, info, warn};

use crate::connection::node_key;
use crate::types::Node;

use super::ClusterNode;

/// Spawns a background task that periodically updates node metrics in etcd.
///
/// The task runs at the specified interval and updates the node's registration
/// in etcd with fresh metrics from the collector. The update uses the existing
/// lease to maintain automatic cleanup on node failure.
///
/// # Arguments
///
/// * `node` - The cluster node to update metrics for
/// * `lease_id` - The etcd lease ID to attach the put operation to
/// * `metrics_healthy_tx` - Channel to signal when metrics updates are failing
///
/// # Returns
///
/// A join handle for the spawned task.
pub(super) fn spawn_metrics_update_task(
    node: &ClusterNode,
    lease_id: i64,
    metrics_healthy_tx: watch::Sender<bool>,
) -> Option<JoinHandle<()>> {
    let interval = node.inner().metrics_update_interval;

    // Don't spawn task if metrics updates are disabled
    if interval == 0 {
        debug!("Metrics update task disabled (interval=0)");
        return None;
    }

    let node_id = node.inner().node_id.clone();
    info!(
        "Starting metrics update task for node {} (interval={}s)",
        node_id, interval
    );

    let node = node.clone();
    let endpoints = node.inner().etcd_endpoints.clone();

    let handle = tokio::spawn(async move {
        let mut interval_timer = tokio::time::interval(Duration::from_secs(interval));
        let mut consecutive_failures: u8 = 0;
        const MAX_FAILURES: u8 = 5;

        // Skip the first tick (immediate) to let initial registration complete
        interval_timer.tick().await;

        loop {
            interval_timer.tick().await;

            // Collect metrics
            let metrics = node.inner().metrics_collector.collect();

            // Skip update if metrics are null (no-op collector)
            if metrics.is_null() {
                debug!("Skipping metrics update (null metrics)");
                continue;
            }

            // Create a new client connection for this update
            // This is intentional - we want an independent connection for metrics
            // to avoid interfering with the main lease keep-alive
            let client = match Client::connect(&endpoints, None).await {
                Ok(c) => c,
                Err(e) => {
                    consecutive_failures += 1;
                    warn!(
                        "Failed to connect to etcd for metrics update (attempt {}): {}",
                        consecutive_failures, e
                    );
                    if consecutive_failures >= MAX_FAILURES {
                        error!(
                            "Metrics update failed {} times, signaling unhealthy",
                            MAX_FAILURES
                        );
                        metrics_healthy_tx.send_replace(false);
                    }
                    continue;
                }
            };

            match update_node_metrics(&node, client, lease_id, metrics.clone()).await {
                Ok(_) => {
                    if consecutive_failures > 0 {
                        debug!(
                            "Metrics update succeeded after {} failures",
                            consecutive_failures
                        );
                        consecutive_failures = 0;
                        // Restore healthy state
                        metrics_healthy_tx.send_replace(true);
                    }
                    debug!(
                        "Node metrics updated successfully: {}",
                        metrics
                    );
                }
                Err(e) => {
                    consecutive_failures += 1;
                    warn!(
                        "Failed to update node metrics (attempt {}): {}",
                        consecutive_failures, e
                    );
                    if consecutive_failures >= MAX_FAILURES {
                        error!(
                            "Metrics update failed {} times, signaling unhealthy",
                            MAX_FAILURES
                        );
                        metrics_healthy_tx.send_replace(false);
                    }
                }
            }
        }
    });

    Some(handle)
}

/// Updates the node's metrics in etcd.
///
/// This function writes the node's current state (including metrics) to etcd,
/// using the existing lease to ensure automatic cleanup on node failure.
async fn update_node_metrics(
    node: &ClusterNode,
    mut client: Client,
    lease_id: i64,
    metrics: serde_json::Value,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let key = node_key(&node.inner().group_name, &node.inner().node_id);
    let last_seen = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_millis() as i64)
        .unwrap_or(0);

    let node_data = Node {
        id: node.inner().node_id.clone(),
        ip: node.inner().node_ip,
        last_seen,
        metadata: metrics,
    };

    let value = serde_json::to_vec(&node_data)?;

    client
        .put(key, value, Some(PutOptions::new().with_lease(lease_id)))
        .await?;

    Ok(())
}

/// Aborts the metrics update task if it exists.
pub(super) async fn abort_metrics_task(handle: Option<JoinHandle<()>>) {
    if let Some(h) = handle {
        h.abort();
        let _ = h.await;
    }
}
