use crate::llm::{self, AppState, ChatMessage, ChatResponse, GenerateRequest, LlmEvent, OllamaChatResponse};
use serde_json::json;
use tauri::ipc::Channel;

// ─── Simple (non-streaming) generate ─────────────────────────

#[tauri::command]
pub async fn generate(state: tauri::State<'_, AppState>, req: GenerateRequest) -> Result<String, String> {
    let config = {
        let guard = state.config.lock().map_err(|e| e.to_string())?;
        guard.clone()
    };

    let client = reqwest::Client::new();
    let messages = llm::build_messages(req.system.as_deref(), &req.prompt);
    let temperature = req.temperature.unwrap_or(config.temperature);
    let max_tokens = req.max_tokens.unwrap_or(config.max_tokens);

    if llm::is_ollama(&config.api_url) {
        call_ollama(&client, &config, &messages, temperature, max_tokens).await
    } else {
        call_openai(&client, &config, &messages, temperature, max_tokens).await
    }
}

// ─── Streaming generate via Tauri Channel ─────────────────────

#[tauri::command]
pub async fn generate_stream(
    state: tauri::State<'_, AppState>,
    req: GenerateRequest,
    channel: Channel<LlmEvent>,
) -> Result<(), String> {
    let config = state.config.lock().map_err(|e| e.to_string())?.clone();

    tauri::async_runtime::spawn(async move {
        let client = reqwest::Client::new();
        let messages = llm::build_messages(req.system.as_deref(), &req.prompt);
        let temperature = req.temperature.unwrap_or(config.temperature);
        let max_tokens = req.max_tokens.unwrap_or(config.max_tokens);

        // For now, we do a non-streaming call and emit the full response as one token
        // Real SSE streaming from Ollama/OpenAI can be added later
        let result = if llm::is_ollama(&config.api_url) {
            call_ollama(&client, &config, &messages, temperature, max_tokens).await
        } else {
            call_openai(&client, &config, &messages, temperature, max_tokens).await
        };

        match result {
            Ok(text) => {
                let _ = channel.send(LlmEvent::Token(text.clone()));
                let _ = channel.send(LlmEvent::Done(text));
            }
            Err(e) => {
                let _ = channel.send(LlmEvent::Error(e));
            }
        }
    });

    Ok(())
}

// ─── Get / Set LLM Config ────────────────────────────────────

#[tauri::command]
pub fn get_llm_config(state: tauri::State<'_, AppState>) -> Result<crate::llm::LlmConfig, String> {
    let config = state.config.lock().map_err(|e| e.to_string())?;
    Ok(config.clone())
}

#[tauri::command]
pub fn set_llm_config(state: tauri::State<'_, AppState>, config: crate::llm::LlmConfig) -> Result<(), String> {
    let mut lock = state.config.lock().map_err(|e| e.to_string())?;
    *lock = config;
    Ok(())
}

// ─── Ollama API ───────────────────────────────────────────────

async fn call_ollama(
    client: &reqwest::Client,
    config: &crate::llm::LlmConfig,
    messages: &[ChatMessage],
    temperature: f32,
    max_tokens: u32,
) -> Result<String, String> {
    let body = json!({
        "model": config.model,
        "messages": messages,
        "stream": false,
        "options": {
            "temperature": temperature,
            "num_predict": max_tokens,
        }
    });

    let url = format!("{}/api/chat", config.api_url.trim_end_matches('/'));

    let res = client
        .post(&url)
        .json(&body)
        .send()
        .await
        .map_err(|e| format!("Ollama request failed: {e}"))?;

    if !res.status().is_success() {
        let status = res.status();
        let text = res.text().await.unwrap_or_default();
        return Err(format!("Ollama error {status}: {text}"));
    }

    let response: OllamaChatResponse = res
        .json()
        .await
        .map_err(|e| format!("Ollama parse error: {e}"))?;

    Ok(response.message.content)
}

// ─── OpenAI-compatible API ────────────────────────────────────

async fn call_openai(
    client: &reqwest::Client,
    config: &crate::llm::LlmConfig,
    messages: &[ChatMessage],
    temperature: f32,
    max_tokens: u32,
) -> Result<String, String> {
    let body = json!({
        "model": config.model,
        "messages": messages,
        "temperature": temperature,
        "max_tokens": max_tokens,
    });

    let url = format!("{}/v1/chat/completions", config.api_url.trim_end_matches('/'));

    let mut req = client.post(&url);
    if !config.api_key.is_empty() {
        req = req.bearer_auth(&config.api_key);
    }

    let res = req
        .json(&body)
        .send()
        .await
        .map_err(|e| format!("API request failed: {e}"))?;

    if !res.status().is_success() {
        let status = res.status();
        let text = res.text().await.unwrap_or_default();
        return Err(format!("API error {status}: {text}"));
    }

    let response: ChatResponse = res
        .json()
        .await
        .map_err(|e| format!("API parse error: {e}"))?;

    response
        .choices
        .first()
        .map(|c| c.message.content.clone())
        .ok_or_else(|| "No response from API".to_string())
}