//! CNI Plugin Invocation
//!
//! This module provides helpers for invoking the PrismNET CNI plugin
//! during pod lifecycle operations.
//!
//! In a production k8s environment, this would be handled by the kubelet
//! on each node. For MVP, we provide test helpers to demonstrate the flow.

use anyhow::{Context, Result};
use serde_json::json;
use std::io::Write;
use std::process::Command;

/// CNI configuration for pod network setup
#[derive(Debug, Clone)]
pub struct CniConfig {
    pub cni_version: String,
    pub name: String,
    pub plugin_type: String,
    pub prismnet_server_addr: String,
    pub subnet_id: String,
    pub org_id: String,
    pub project_id: String,
}

impl Default for CniConfig {
    fn default() -> Self {
        Self {
            cni_version: "1.0.0".to_string(),
            name: "k8shost-net".to_string(),
            plugin_type: "prismnet".to_string(),
            prismnet_server_addr: "http://127.0.0.1:9090".to_string(),
            subnet_id: String::new(),
            org_id: String::new(),
            project_id: String::new(),
        }
    }
}

/// Invoke CNI ADD command to set up pod networking
///
/// This creates a network port in PrismNET and returns the allocated IP/MAC.
/// In production, this would be called by the kubelet on the node where the pod runs.
pub async fn invoke_cni_add(
    config: &CniConfig,
    container_id: &str,
    netns: &str,
    ifname: &str,
) -> Result<CniResult> {
    // Build CNI config JSON
    let cni_config = json!({
        "cniVersion": config.cni_version,
        "name": config.name,
        "type": config.plugin_type,
        "prismnet": {
            "server_addr": config.prismnet_server_addr,
            "subnet_id": config.subnet_id,
            "org_id": config.org_id,
            "project_id": config.project_id,
        }
    });

    // Find CNI plugin binary
    let cni_path = std::env::var("CNI_PLUGIN_PATH")
        .unwrap_or_else(|_| "/opt/cni/bin/prismnet-cni".to_string());

    // Invoke CNI plugin
    let mut child = Command::new(&cni_path)
        .env("CNI_COMMAND", "ADD")
        .env("CNI_CONTAINERID", container_id)
        .env("CNI_NETNS", netns)
        .env("CNI_IFNAME", ifname)
        .env("CNI_PATH", "/opt/cni/bin")
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .context("Failed to spawn CNI plugin")?;

    // Write config to stdin
    if let Some(mut stdin) = child.stdin.take() {
        stdin
            .write_all(cni_config.to_string().as_bytes())
            .context("Failed to write CNI config to stdin")?;
    }

    // Wait for result
    let output = child
        .wait_with_output()
        .context("Failed to wait for CNI plugin")?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        return Err(anyhow::anyhow!("CNI ADD failed: {}", stderr));
    }

    // Parse result
    let result: CniResult =
        serde_json::from_slice(&output.stdout).context("Failed to parse CNI result")?;

    Ok(result)
}

/// Invoke CNI DEL command to tear down pod networking
///
/// This removes the network port from PrismNET.
pub async fn invoke_cni_del(
    config: &CniConfig,
    container_id: &str,
    netns: &str,
    ifname: &str,
) -> Result<()> {
    // Build CNI config JSON
    let cni_config = json!({
        "cniVersion": config.cni_version,
        "name": config.name,
        "type": config.plugin_type,
        "prismnet": {
            "server_addr": config.prismnet_server_addr,
            "subnet_id": config.subnet_id,
            "org_id": config.org_id,
            "project_id": config.project_id,
        }
    });

    // Find CNI plugin binary
    let cni_path = std::env::var("CNI_PLUGIN_PATH")
        .unwrap_or_else(|_| "/opt/cni/bin/prismnet-cni".to_string());

    // Invoke CNI plugin
    let mut child = Command::new(&cni_path)
        .env("CNI_COMMAND", "DEL")
        .env("CNI_CONTAINERID", container_id)
        .env("CNI_NETNS", netns)
        .env("CNI_IFNAME", ifname)
        .env("CNI_PATH", "/opt/cni/bin")
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .context("Failed to spawn CNI plugin")?;

    // Write config to stdin
    if let Some(mut stdin) = child.stdin.take() {
        stdin
            .write_all(cni_config.to_string().as_bytes())
            .context("Failed to write CNI config to stdin")?;
    }

    // Wait for result
    let output = child
        .wait_with_output()
        .context("Failed to wait for CNI plugin")?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        tracing::warn!(
            "CNI DEL error (may be expected if already deleted): {}",
            stderr
        );
    }

    Ok(())
}

/// CNI result structure
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct CniResult {
    #[serde(rename = "cniVersion")]
    pub cni_version: String,
    pub interfaces: Vec<CniInterface>,
    pub ips: Vec<CniIpConfig>,
    pub routes: Vec<CniRoute>,
    pub dns: CniDnsConfig,
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct CniInterface {
    pub name: String,
    pub mac: String,
    pub sandbox: String,
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct CniIpConfig {
    pub interface: usize,
    pub address: String,
    pub gateway: String,
}

#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct CniRoute {
    pub dst: String,
    pub gw: String,
}

#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)]
pub struct CniDnsConfig {
    pub nameservers: Vec<String>,
}
