//! NATS subscriber for `provisioning.vault.lease.request` events.
//!
//! Flow:
//!   1. Orchestrator publishes `vault.lease.request` with `{secret_path,
//!      session_id, ttl_secs}`
//!   2. This handler creates a lease in `LeaseStore`
//!   3. Publishes `vault.lease.issued` with `{lease_id, ttl_secs}` — NO
//!      credential
//!   4. Orchestrator fetches the credential via HTTPS GET
//!      /api/v1/leases/{lease_id}/credential

use std::sync::Arc;
use std::time::Duration;

use futures::StreamExt;
use platform_nats::NatsBridge;
use serde::{Deserialize, Serialize};
use tracing::{error, info, warn};

use super::LeaseStore;

const STREAM_NAME: &str = "VAULT";
const CONSUMER_NAME: &str = "vault-lease-handler";

/// Request payload from orchestrator.
#[derive(Debug, Deserialize)]
struct LeaseRequest {
    secret_path: String,
    session_id: String,
    ttl_secs: u64,
    /// Human-readable label for audit purposes.
    description: Option<String>,
}

/// Response published back to orchestrator.
#[derive(Debug, Serialize)]
struct LeaseIssued {
    lease_id: String,
    ttl_secs: u64,
    /// Echoed for correlation.
    session_id: String,
}

/// Spawn the NATS lease request handler as a background tokio task.
pub fn spawn_lease_handler(
    nats: Arc<NatsBridge>,
    store: Arc<LeaseStore>,
    secret_resolver: Arc<dyn SecretResolver>,
) {
    tokio::spawn(async move {
        run_lease_handler(nats, store, secret_resolver).await;
    });
}

async fn run_lease_handler(
    nats: Arc<NatsBridge>,
    store: Arc<LeaseStore>,
    secret_resolver: Arc<dyn SecretResolver>,
) {
    let mut messages = match nats.subscribe_pull(STREAM_NAME, CONSUMER_NAME).await {
        Ok(m) => m,
        Err(e) => {
            error!("vault lease handler: subscribe failed — {e}");
            return;
        }
    };

    info!("Vault NATS lease handler running on stream {STREAM_NAME}");

    while let Some(msg_result) = messages.next().await {
        match msg_result {
            Ok(msg) => {
                match serde_json::from_slice::<LeaseRequest>(&msg.payload) {
                    Ok(req) => {
                        let description =
                            req.description.unwrap_or_else(|| req.secret_path.clone());

                        match secret_resolver.resolve(&req.secret_path).await {
                            Ok(value) => {
                                let cred = super::Credential {
                                    value,
                                    description: description.clone(),
                                };
                                let ttl = Duration::from_secs(req.ttl_secs.max(60).min(86400));
                                let (lease_id, ttl_secs) =
                                    store.issue(req.secret_path, req.session_id.clone(), cred, ttl);

                                let issued = LeaseIssued {
                                    lease_id,
                                    ttl_secs,
                                    session_id: req.session_id,
                                };
                                if let Err(e) =
                                    nats.publish_json("vault.lease.issued", &issued).await
                                {
                                    error!("vault lease handler: publish issued failed — {e}");
                                }
                                info!(description = %description, "Lease issued");
                            }
                            Err(e) => {
                                warn!("vault lease handler: secret not found for path — {e}");
                            }
                        }
                    }
                    Err(e) => {
                        warn!("vault lease handler: deserialize failed — {e}");
                    }
                }

                if let Err(e) = msg.ack().await {
                    warn!("vault lease handler: ack failed — {e}");
                }
            }
            Err(e) => {
                error!("vault lease handler: message error — {e}");
            }
        }
    }
}

/// Trait for resolving a secret path to its plaintext credential value.
///
/// Implemented by the vault service against its secret backend.
#[async_trait::async_trait]
pub trait SecretResolver: Send + Sync {
    async fn resolve(&self, path: &str) -> anyhow::Result<String>;
}
