use dioxus::prelude::*;
#[cfg(feature = "server")]
use serde::Deserialize;

use crate::models::AgentEntry;

/// Raw assistant object returned by the LangGraph `POST /assistants/search`
/// endpoint. Only the fields we display are deserialized; unknown keys are
/// silently ignored thanks to serde defaults.
#[cfg(feature = "server")]
#[derive(Deserialize)]
struct LangGraphAssistant {
    assistant_id: String,
    #[serde(default)]
    name: String,
    #[serde(default)]
    graph_id: String,
    #[serde(default)]
    metadata: serde_json::Value,
}

/// Fetch the list of assistants (agents) from a LangGraph instance.
///
/// Calls `POST <langgraph_url>/assistants/search` with an empty body to
/// retrieve every registered assistant. Each result is mapped to the
/// frontend-friendly `AgentEntry` model.
///
/// # Returns
///
/// A vector of `AgentEntry` structs. Returns an empty vector when the
/// LangGraph URL is not configured or the service is unreachable.
///
/// # Errors
///
/// Returns `ServerFnError` on network or deserialization failures that
/// indicate a misconfigured (but present) LangGraph instance.
#[server(endpoint = "list-langgraph-agents")]
pub async fn list_langgraph_agents() -> Result<Vec<AgentEntry>, ServerFnError> {
    let state: crate::infrastructure::ServerState =
        dioxus_fullstack::FullstackContext::extract().await?;

    let base_url = state.services.langgraph_url.clone();
    if base_url.is_empty() {
        return Ok(Vec::new());
    }

    let url = format!("{}/assistants/search", base_url.trim_end_matches('/'));

    let client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(5))
        .build()
        .map_err(|e| ServerFnError::new(format!("HTTP client error: {e}")))?;

    // LangGraph expects a POST with a JSON body (empty object = no filters).
    let resp = match client
        .post(&url)
        .header("content-type", "application/json")
        .body("{}")
        .send()
        .await
    {
        Ok(r) if r.status().is_success() => r,
        Ok(r) => {
            let status = r.status();
            let body = r.text().await.unwrap_or_default();
            tracing::error!("LangGraph returned {status}: {body}");
            return Ok(Vec::new());
        }
        Err(e) => {
            tracing::error!("LangGraph request failed: {e}");
            return Ok(Vec::new());
        }
    };

    let assistants: Vec<LangGraphAssistant> = resp
        .json()
        .await
        .map_err(|e| ServerFnError::new(format!("Failed to parse LangGraph response: {e}")))?;

    let entries = assistants
        .into_iter()
        .map(|a| {
            // Use the assistant name if present, otherwise fall back to graph_id.
            let name = if a.name.is_empty() {
                a.graph_id.clone()
            } else {
                a.name
            };

            // Extract a description from metadata if available.
            let description = a
                .metadata
                .get("description")
                .and_then(|v| v.as_str())
                .unwrap_or("")
                .to_string();

            AgentEntry {
                id: a.assistant_id,
                name,
                description,
                status: "active".to_string(),
            }
        })
        .collect();

    Ok(entries)
}
