use serde::Deserialize;

use crate::agent::{
    AgentConfigStatus, LocalModelList, LocalProviderHealthStatus, SaveAgentRuntimeConfigRequest,
    UpdateRemoteApiKeyRequest,
};
use crate::state::AppState;

/// Return the current public configuration state for the AI chat runtime.
#[tauri::command]
pub async fn get_agent_config_status(
    state: tauri::State<'_, AppState>,
) -> Result<AgentConfigStatus, String> {
    let agent = state
        .agent
        .lock()
        .map_err(|_| "agent state is unavailable".to_string())?;

    agent.get_config_status().map_err(|error| error.to_string())
}

/// Persist the non-secret provider and task routing settings.
#[tauri::command]
pub async fn save_agent_runtime_config(
    state: tauri::State<'_, AppState>,
    request: SaveAgentRuntimeConfigRequest,
) -> Result<AgentConfigStatus, String> {
    let mut agent = state
        .agent
        .lock()
        .map_err(|_| "agent state is unavailable".to_string())?;

    agent
        .save_runtime_config(request)
        .map_err(|error| error.to_string())
}

/// Save or replace the plaintext remote API key.
#[tauri::command]
pub async fn update_remote_api_key(
    state: tauri::State<'_, AppState>,
    request: UpdateRemoteApiKeyRequest,
) -> Result<AgentConfigStatus, String> {
    let mut agent = state
        .agent
        .lock()
        .map_err(|_| "agent state is unavailable".to_string())?;

    agent
        .update_remote_api_key(request)
        .map_err(|error| error.to_string())
}

/// Remove the stored plaintext remote API key.
#[tauri::command]
pub async fn clear_remote_api_key(
    state: tauri::State<'_, AppState>,
) -> Result<AgentConfigStatus, String> {
    let mut agent = state
        .agent
        .lock()
        .map_err(|_| "agent state is unavailable".to_string())?;

    agent
        .clear_remote_api_key()
        .map_err(|error| error.to_string())
}

/// Lists local models from the running Mistral HTTP endpoint and stored settings.
#[tauri::command]
pub async fn list_local_models(
    state: tauri::State<'_, AppState>,
) -> Result<LocalModelList, String> {
    let status = {
        let agent = state
            .agent
            .lock()
            .map_err(|_| "agent state is unavailable".to_string())?;
        agent
            .get_config_status()
            .map_err(|error| error.to_string())?
    };

    let discovered = fetch_local_models(&status.local_base_url).await;
    let mut models = status.local_available_models;

    if let Ok(mut discovered_models) = discovered {
        for model in discovered_models.drain(..) {
            if !models.iter().any(|existing| existing == &model) {
                models.push(model);
            }
        }

        return Ok(LocalModelList {
            reachable: true,
            models,
        });
    }

    Ok(LocalModelList {
        reachable: false,
        models,
    })
}

/// Checks whether the local Mistral HTTP provider is reachable.
#[tauri::command]
pub async fn check_local_provider_health(
    state: tauri::State<'_, AppState>,
) -> Result<LocalProviderHealthStatus, String> {
    let local_base_url = {
        let agent = state
            .agent
            .lock()
            .map_err(|_| "agent state is unavailable".to_string())?;
        agent
            .get_config_status()
            .map_err(|error| error.to_string())?
            .local_base_url
    };

    match fetch_local_models(&local_base_url).await {
        Ok(models) => Ok(LocalProviderHealthStatus {
            reachable: true,
            message: format!("Local provider reachable with {} model(s).", models.len()),
        }),
        Err(error) => Ok(LocalProviderHealthStatus {
            reachable: false,
            message: error,
        }),
    }
}

#[derive(Debug, Deserialize)]
struct OpenAiModelListResponse {
    data: Vec<OpenAiModelDescriptor>,
}

#[derive(Debug, Deserialize)]
struct OpenAiModelDescriptor {
    id: String,
}

async fn fetch_local_models(base_url: &str) -> Result<Vec<String>, String> {
    let base_url = base_url.trim_end_matches('/');
    let endpoint = format!("{base_url}/v1/models");
    let response = reqwest::get(&endpoint)
        .await
        .map_err(|error| format!("Failed to reach local provider: {error}"))?;

    if !response.status().is_success() {
        return Err(format!(
            "Local provider health check failed with status {}",
            response.status()
        ));
    }

    let payload = response
        .json::<OpenAiModelListResponse>()
        .await
        .map_err(|error| format!("Failed to parse local provider model list: {error}"))?;

    Ok(payload.data.into_iter().map(|model| model.id).collect())
}
