//! Daemon-forwarded admin RPC.
//!
//! Phase 81.33.b.real Stage 4. Daemon receives `nexo/admin/voice/
//! <verb>` and forwards to `plugin.voice.admin.<verb>`. Four verbs:
//!   - `restart_provider` — drain + re-init named provider pool.
//!   - `drain`            — cancel in-flight + stop accepting.
//!   - `test_synth`       — operator preview (bypasses store).
//!   - `stats`            — provider/cache/pool snapshot.

use serde::Deserialize;
use serde_json::{json, Value};

use crate::error::{Result, VoiceError};
use crate::tools::VoiceToolContext;

pub async fn dispatch(ctx: &VoiceToolContext, verb: &str, args: Value) -> Result<Value> {
    match verb {
        "restart_provider" => restart_provider(ctx, args).await,
        "drain" => drain(ctx, args).await,
        "test_synth" => test_synth(ctx, args).await,
        "stats" => stats(ctx, args).await,
        other => Err(VoiceError::NotSupported(Box::leak(
            format!("unknown admin verb `{other}`").into_boxed_str(),
        ))),
    }
}

#[derive(Debug, Deserialize)]
struct RestartArgs {
    provider_id: String,
}

async fn restart_provider(ctx: &VoiceToolContext, args: Value) -> Result<Value> {
    let parsed: RestartArgs = serde_json::from_value(args)
        .map_err(|e| VoiceError::InvalidParam(format!("restart_provider: {e}")))?;
    if !ctx.providers.iter().any(|p| p.id() == parsed.provider_id) {
        return Ok(json!({ "error": "unknown_provider", "provider_id": parsed.provider_id }));
    }
    // Pool reset is a follow-up — Edge provider currently opens a
    // fresh WS per synth, so there's nothing to drain. ElevenLabs +
    // others get connection pools in their full impl.
    tracing::info!(provider = %parsed.provider_id, "voice/admin: restart_provider (no-op v1)");
    Ok(json!({ "ok": true, "provider_id": parsed.provider_id, "drained": 0 }))
}

#[derive(Debug, Deserialize, Default)]
#[serde(default)]
struct DrainArgs {
    #[allow(dead_code)]
    timeout_ms: Option<u64>,
}

async fn drain(_ctx: &VoiceToolContext, args: Value) -> Result<Value> {
    let _parsed: DrainArgs = serde_json::from_value(args)
        .map_err(|e| VoiceError::InvalidParam(format!("drain: {e}")))?;
    // Synth tasks are joined per-call (no long-lived workers); drain
    // is effectively instantaneous. Returns 0 in-flight always v1.
    Ok(json!({ "ok": true, "drained": 0, "in_flight": 0 }))
}

#[derive(Debug, Deserialize)]
struct TestSynthArgs {
    text: String,
    voice_id: String,
    #[serde(default)]
    provider: Option<String>,
}

async fn test_synth(ctx: &VoiceToolContext, args: Value) -> Result<Value> {
    use base64::Engine;
    let parsed: TestSynthArgs = serde_json::from_value(args)
        .map_err(|e| VoiceError::InvalidParam(format!("test_synth: {e}")))?;
    let outcome = crate::tools::invoke_pipeline(
        ctx,
        &parsed.text,
        &parsed.voice_id,
        parsed.provider.as_deref(),
        None,
        None,
        None,
        None,
    )
    .await?;
    let audio_b64 = match &outcome.reply {
        nexo_tool_meta::reply_kind::OutboundReplyKind::VoiceNote { audio_bytes, .. } => {
            Some(base64::engine::general_purpose::STANDARD.encode(audio_bytes))
        }
        _ => None,
    };
    Ok(json!({
        "ok": true,
        "audio_b64": audio_b64,
        "winner": outcome.winner,
        "latency_ms": outcome.latency_ms,
        "reason_code": outcome.reason_code.as_metric_label(),
    }))
}

async fn stats(ctx: &VoiceToolContext, _args: Value) -> Result<Value> {
    let providers: Vec<Value> = ctx
        .providers
        .iter()
        .map(|p| {
            json!({
                "id": p.id(),
                "label": p.label(),
                "configured": p.is_configured(),
                "cost_class": p.cost_class().as_str(),
                "breaker_open": ctx.breakers.is_open(p.id()),
            })
        })
        .collect();
    Ok(json!({
        "ok": true,
        "providers": providers,
        "cache": {
            "bytes": ctx.cache.current_bytes(),
            "entries": ctx.cache.len(),
        },
        "fallback_strategy": format!("{:?}", ctx.fallback.strategy),
        "breakers": ctx.breakers.snapshot()
            .into_iter()
            .map(|(id, open)| json!({ "id": id, "open": open }))
            .collect::<Vec<_>>(),
        "policy": {
            "enabled": ctx.policy.enabled,
            "allow_toggle": ctx.policy.allow_toggle,
            "allow_voice": ctx.policy.allow_voice,
        },
    }))
}
