use std::path::{Path, PathBuf};

use futures::StreamExt as _;
use serde::{Deserialize, Serialize};

const SUBJECT: &str = "provisioning.component.lifecycle";

#[derive(Debug, Deserialize, Serialize)]
struct ComponentLifecycleEvent {
    task_id: String,
    component: String,
    server: String,
    workspace: String,
    operation: String,
    status: String,
}

/// Background task: subscribe to `provisioning.component.lifecycle` and
/// update `.provisioning-state.ncl` for each terminal event.
/// Exits silently when NATS is unreachable — daemon continues normally.
pub async fn run(prov_root: PathBuf) {
    let url = std::env::var("NATS_URL").unwrap_or_else(|_| "nats://localhost:4222".to_string());

    let client = match async_nats::connect(&url).await {
        Ok(c) => c,
        Err(e) => {
            tracing::info!("NATS unavailable ({e}) — component lifecycle subscriber inactive");
            return;
        }
    };

    let mut sub = match client.subscribe(SUBJECT).await {
        Ok(s) => s,
        Err(e) => {
            tracing::error!("NATS subscribe to {SUBJECT} failed: {e}");
            return;
        }
    };

    tracing::info!("NATS component lifecycle subscriber active on {SUBJECT}");

    while let Some(msg) = sub.next().await {
        let event: ComponentLifecycleEvent = match serde_json::from_slice(&msg.payload) {
            Ok(e) => e,
            Err(e) => {
                tracing::warn!("Malformed component lifecycle event: {e}");
                continue;
            }
        };
        apply_state(&event, &prov_root).await;
    }
}

async fn apply_state(event: &ComponentLifecycleEvent, prov_root: &Path) {
    let nu_bin = find_nu_binary();
    let nulib = prov_root.join("core/nulib");

    // Map (status, operation) → Nu function call
    let nu_call = match (event.status.as_str(), event.operation.as_str()) {
        ("completed", "delete") => format!(
            "use domain/workspace/state.nu [state-node-delete]; \
             state-node-delete '{}' '{}' '{}'",
            event.workspace, event.server, event.component,
        ),
        ("completed", "reinstall") => format!(
            "use domain/workspace/state.nu [state-node-reset]; \
             state-node-reset '{}' '{}' '{}' --source orchestrator",
            event.workspace, event.server, event.component,
        ),
        ("completed", _) => format!(
            "use domain/workspace/state.nu [state-node-finish]; \
             state-node-finish '{}' '{}' '{}' --success --source orchestrator",
            event.workspace, event.server, event.component,
        ),
        _ => format!(
            "use domain/workspace/state.nu [state-node-finish]; \
             state-node-finish '{}' '{}' '{}' --source orchestrator",
            event.workspace, event.server, event.component,
        ),
    };

    let result = tokio::process::Command::new(&nu_bin)
        .env("PROVISIONING", prov_root)
        .arg("--include-path")
        .arg(&nulib)
        .arg("-c")
        .arg(&nu_call)
        .output()
        .await;

    match result {
        Ok(out) if out.status.success() => tracing::info!(
            component = %event.component,
            server    = %event.server,
            operation = %event.operation,
            status    = %event.status,
            "DAG state updated from NATS lifecycle event"
        ),
        Ok(out) => tracing::warn!(
            component = %event.component,
            server    = %event.server,
            "DAG state update failed: {}",
            String::from_utf8_lossy(&out.stderr).trim()
        ),
        Err(e) => tracing::error!("Failed to spawn nu for DAG state update: {e}"),
    }
}

fn find_nu_binary() -> PathBuf {
    for candidate in ["/usr/local/bin/nu", "/opt/homebrew/bin/nu", "/usr/bin/nu"] {
        let p = PathBuf::from(candidate);
        if p.exists() {
            return p;
        }
    }
    if let Ok(out) = std::process::Command::new("which").arg("nu").output() {
        if out.status.success() {
            let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
            if !s.is_empty() {
                return PathBuf::from(s);
            }
        }
    }
    PathBuf::from("nu")
}
