//! PrismNET CNI Plugin for k8shost
//!
//! This binary implements the CNI 1.0.0 specification to integrate k8shost pods
//! with PrismNET's OVN-based virtual networking.
//!
//! CNI operations:
//! - ADD: Create network interface and attach to OVN logical switch
//! - DEL: Remove network interface and clean up OVN resources
//! - CHECK: Verify network configuration is correct
//! - VERSION: Report supported CNI versions

use anyhow::{Context, Result};
use prismnet_api::{
    port_service_client::PortServiceClient, AttachDeviceRequest, CreatePortRequest,
    DeletePortRequest, DeviceType, ListPortsRequest,
};
use serde::{Deserialize, Serialize};
use std::io::{self, Read};

#[derive(Debug, Serialize, Deserialize)]
struct CniConfig {
    #[serde(rename = "cniVersion")]
    cni_version: String,
    name: String,
    #[serde(rename = "type")]
    plugin_type: String,
    #[serde(default)]
    prismnet: PrismNETConfig,
}

#[derive(Debug, Default, Serialize, Deserialize)]
struct PrismNETConfig {
    server_addr: String,
    subnet_id: String,
    org_id: String,
    project_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct CniResult {
    #[serde(rename = "cniVersion")]
    cni_version: String,
    interfaces: Vec<Interface>,
    ips: Vec<IpConfig>,
    routes: Vec<Route>,
    dns: DnsConfig,
}

#[derive(Debug, Serialize, Deserialize)]
struct Interface {
    name: String,
    mac: String,
    sandbox: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct IpConfig {
    interface: usize,
    address: String,
    gateway: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct Route {
    dst: String,
    gw: String,
}

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

#[tokio::main]
async fn main() -> Result<()> {
    tracing_subscriber::fmt()
        .with_writer(std::io::stderr)
        .init();

    let command = std::env::var("CNI_COMMAND").context("CNI_COMMAND not set")?;

    match command.as_str() {
        "ADD" => handle_add().await,
        "DEL" => handle_del().await,
        "CHECK" => handle_check(),
        "VERSION" => handle_version(),
        _ => Err(anyhow::anyhow!("Unknown CNI command: {}", command)),
    }
}

async fn handle_add() -> Result<()> {
    // Read CNI config from stdin
    let mut buffer = String::new();
    io::stdin().read_to_string(&mut buffer)?;
    let config: CniConfig = serde_json::from_str(&buffer)?;

    // Parse CNI environment variables
    let container_id = std::env::var("CNI_CONTAINERID").context("CNI_CONTAINERID not set")?;
    let netns = std::env::var("CNI_NETNS").context("CNI_NETNS not set")?;
    let ifname = std::env::var("CNI_IFNAME").context("CNI_IFNAME not set")?;

    tracing::info!(
        container_id = %container_id,
        netns = %netns,
        ifname = %ifname,
        "CNI ADD operation starting"
    );

    // Connect to PrismNET server
    let prismnet_addr = if config.prismnet.server_addr.is_empty() {
        std::env::var("PRISMNET_SERVER_ADDR")
            .unwrap_or_else(|_| "http://127.0.0.1:9090".to_string())
    } else {
        config.prismnet.server_addr.clone()
    };

    let mut port_client = PortServiceClient::connect(prismnet_addr.clone())
        .await
        .context("Failed to connect to PrismNET server")?;

    // Extract tenant context from config or environment
    let org_id = if !config.prismnet.org_id.is_empty() {
        config.prismnet.org_id.clone()
    } else {
        std::env::var("K8SHOST_ORG_ID").unwrap_or_else(|_| "default-org".to_string())
    };

    let project_id = if !config.prismnet.project_id.is_empty() {
        config.prismnet.project_id.clone()
    } else {
        std::env::var("K8SHOST_PROJECT_ID").unwrap_or_else(|_| "default-project".to_string())
    };

    let subnet_id = if !config.prismnet.subnet_id.is_empty() {
        config.prismnet.subnet_id.clone()
    } else {
        std::env::var("K8SHOST_SUBNET_ID").context("subnet_id not configured")?
    };

    // Create port in PrismNET
    let port_name = format!("pod-{}", container_id);
    let create_req = CreatePortRequest {
        org_id: org_id.clone(),
        project_id: project_id.clone(),
        subnet_id: subnet_id.clone(),
        name: port_name.clone(),
        description: format!("k8shost pod {} network port", container_id),
        ip_address: String::new(), // Let PrismNET auto-allocate
        security_group_ids: vec![],
    };

    let create_resp = port_client
        .create_port(create_req)
        .await
        .context("Failed to create PrismNET port")?
        .into_inner();

    let port = create_resp.port.context("Port not returned in response")?;

    tracing::info!(
        port_id = %port.id,
        ip_address = %port.ip_address,
        mac_address = %port.mac_address,
        "PrismNET port created successfully"
    );

    // Attach device_id so DEL can locate the port via device_id filter.
    let attach_req = AttachDeviceRequest {
        org_id: org_id.clone(),
        project_id: project_id.clone(),
        subnet_id: subnet_id.clone(),
        port_id: port.id.clone(),
        device_id: container_id.clone(),
        device_type: DeviceType::Other as i32,
    };

    if let Err(e) = port_client.attach_device(attach_req).await {
        tracing::warn!(
            port_id = %port.id,
            error = %e,
            "Failed to attach device_id to PrismNET port"
        );
    }

    // TODO: In production, we would:
    // 1. Create veth pair
    // 2. Move one end to container network namespace
    // 3. Configure IP address and routes
    // 4. Configure OVN logical switch port with MAC/IP
    //
    // For MVP, we return the allocated IP/MAC information

    // Extract gateway from subnet (would come from GetSubnet call in production)
    let gateway = port.ip_address.split('.').take(3).collect::<Vec<_>>().join(".") + ".1";

    // Return CNI result
    let result = CniResult {
        cni_version: config.cni_version,
        interfaces: vec![Interface {
            name: ifname.clone(),
            mac: port.mac_address.clone(),
            sandbox: netns,
        }],
        ips: vec![IpConfig {
            interface: 0,
            address: format!("{}/24", port.ip_address),
            gateway: gateway.clone(),
        }],
        routes: vec![Route {
            dst: "0.0.0.0/0".to_string(),
            gw: gateway,
        }],
        dns: DnsConfig {
            nameservers: vec!["8.8.8.8".to_string(), "8.8.4.4".to_string()],
        },
    };

    println!("{}", serde_json::to_string(&result)?);

    tracing::info!("CNI ADD operation completed successfully");
    Ok(())
}

async fn handle_del() -> Result<()> {
    // Read CNI config from stdin
    let mut buffer = String::new();
    io::stdin().read_to_string(&mut buffer)?;
    let config: CniConfig = serde_json::from_str(&buffer)?;

    // Parse CNI environment variables
    let container_id = std::env::var("CNI_CONTAINERID").context("CNI_CONTAINERID not set")?;

    tracing::info!(
        container_id = %container_id,
        "CNI DEL operation starting"
    );

    // Connect to PrismNET server
    let prismnet_addr = if config.prismnet.server_addr.is_empty() {
        std::env::var("PRISMNET_SERVER_ADDR")
            .unwrap_or_else(|_| "http://127.0.0.1:9090".to_string())
    } else {
        config.prismnet.server_addr.clone()
    };

    let mut port_client = PortServiceClient::connect(prismnet_addr.clone())
        .await
        .context("Failed to connect to PrismNET server")?;

    // Extract tenant context
    let org_id = if !config.prismnet.org_id.is_empty() {
        config.prismnet.org_id.clone()
    } else {
        std::env::var("K8SHOST_ORG_ID").unwrap_or_else(|_| "default-org".to_string())
    };

    let project_id = if !config.prismnet.project_id.is_empty() {
        config.prismnet.project_id.clone()
    } else {
        std::env::var("K8SHOST_PROJECT_ID").unwrap_or_else(|_| "default-project".to_string())
    };

    let subnet_id = if !config.prismnet.subnet_id.is_empty() {
        config.prismnet.subnet_id.clone()
    } else {
        std::env::var("K8SHOST_SUBNET_ID").unwrap_or_default()
    };

    // Find port by container ID using device_id filter
    // List ports to find our port ID
    let list_req = ListPortsRequest {
        org_id: org_id.clone(),
        project_id: project_id.clone(),
        subnet_id: subnet_id.clone(),
        device_id: container_id.clone(),
        page_size: 10,
        page_token: String::new(),
    };

    let list_resp = port_client.list_ports(list_req).await;

    if let Ok(resp) = list_resp {
        let ports = resp.into_inner().ports;
        if let Some(port) = ports.first() {
            // Delete the port
            let delete_req = DeletePortRequest {
                org_id,
                project_id,
                subnet_id,
                id: port.id.clone(),
            };

            port_client
                .delete_port(delete_req)
                .await
                .context("Failed to delete PrismNET port")?;

            tracing::info!(
                port_id = %port.id,
                "PrismNET port deleted successfully"
            );
        }
    }

    // TODO: In production, we would also:
    // 1. Remove network interfaces from container namespace
    // 2. Clean up veth pair
    // 3. Remove OVN logical switch port configuration

    tracing::info!("CNI DEL operation completed successfully");
    Ok(())
}

fn handle_check() -> Result<()> {
    // TODO: Implement CHECK logic
    // Verify that the network configuration is still valid
    // For now, return success

    tracing::info!("CNI CHECK operation - basic validation passed");
    Ok(())
}

fn handle_version() -> Result<()> {
    let version = serde_json::json!({
        "cniVersion": "1.0.0",
        "supportedVersions": ["0.3.0", "0.3.1", "0.4.0", "1.0.0"]
    });

    println!("{}", serde_json::to_string(&version)?);
    Ok(())
}
