use crate::error::AppError;
use crate::models::agent::Agent;
use crate::models::agent_task::AgentTask;
use crate::models::module::{ProjectModule, MODULE_AGENT_TASK_RUNNER};
use crate::models::project::Project;
use crate::AppState;
use tauri::State;

const TASK_LIST_RESULT_PREVIEW_MAX_BYTES: usize = 20_000;
const TASK_LIST_ERROR_PREVIEW_MAX_BYTES: usize = 8_000;
const TASK_LIST_TRUNCATION_NOTICE: &str =
    "\n\n[... contenu tronque pour preserver la fluidite de l'interface ...]";

fn truncate_for_task_list(value: Option<String>, max_bytes: usize) -> Option<String> {
    let Some(content) = value else {
        return None;
    };

    if content.len() <= max_bytes {
        return Some(content);
    }

    let mut boundary = max_bytes;
    while boundary > 0 && !content.is_char_boundary(boundary) {
        boundary -= 1;
    }

    let mut truncated = content[..boundary].to_string();
    truncated.push_str(TASK_LIST_TRUNCATION_NOTICE);
    Some(truncated)
}

#[tauri::command]
pub fn create_agent_task(
    state: State<'_, AppState>,
    project_id: String,
    agent_id: String,
    title: String,
    description: String,
) -> Result<AgentTask, AppError> {
    if title.trim().is_empty() {
        return Err(AppError::from(
            "Le titre de la tâche est obligatoire".to_string(),
        ));
    }

    let db = state
        .db
        .lock()
        .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;

    let enabled = ProjectModule::is_enabled(&db, &project_id, MODULE_AGENT_TASK_RUNNER)?;
    if !enabled {
        return Err(AppError::from(
            "Le module Agent task runner est désactivé pour ce projet".to_string(),
        ));
    }

    Project::get_by_id(&db, &project_id)?;
    Agent::get_by_id(&db, &agent_id)?;

    let task = AgentTask::insert(
        &db,
        &project_id,
        &agent_id,
        title.trim(),
        description.trim(),
    )?;
    Ok(task)
}

#[tauri::command]
pub fn list_agent_tasks(
    state: State<'_, AppState>,
    project_id: String,
) -> Result<Vec<AgentTask>, AppError> {
    let db = state
        .db
        .lock()
        .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;

    let tasks = AgentTask::list_by_project(&db, &project_id)?
        .into_iter()
        .map(|mut task| {
            task.result = truncate_for_task_list(task.result, TASK_LIST_RESULT_PREVIEW_MAX_BYTES);
            task.error = truncate_for_task_list(task.error, TASK_LIST_ERROR_PREVIEW_MAX_BYTES);
            task
        })
        .collect();
    Ok(tasks)
}

#[tauri::command]
pub fn retry_agent_task(state: State<'_, AppState>, task_id: String) -> Result<(), AppError> {
    let db = state
        .db
        .lock()
        .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;

    let task = AgentTask::get_by_id(&db, &task_id)?;
    let enabled = ProjectModule::is_enabled(&db, &task.project_id, MODULE_AGENT_TASK_RUNNER)?;
    if !enabled {
        return Err(AppError::from(
            "Le module Agent task runner est désactivé pour ce projet".to_string(),
        ));
    }

    if task.status != "error" && task.status != "cancelled" && task.status != "done" {
        return Err(AppError::from(format!(
            "Impossible de relancer une tâche avec le statut '{}'",
            task.status
        )));
    }

    AgentTask::retry(&db, &task_id)?;
    Ok(())
}

#[tauri::command]
pub async fn cancel_agent_task(
    state: State<'_, AppState>,
    task_id: String,
) -> Result<(), AppError> {
    {
        let db = state
            .db
            .lock()
            .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;

        let task = AgentTask::get_by_id(&db, &task_id)?;
        if task.status == "done" || task.status == "cancelled" {
            return Err(AppError::from(format!(
                "Impossible d'annuler une tâche avec le statut '{}'",
                task.status
            )));
        }
    }

    state.process_registry.cancel_task(&task_id).await;

    let db = state
        .db
        .lock()
        .map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
    let task = AgentTask::get_by_id(&db, &task_id)?;
    if task.status == "done" || task.status == "cancelled" {
        return Err(AppError::from(format!(
            "Impossible d'annuler une tâche avec le statut '{}'",
            task.status
        )));
    }

    AgentTask::cancel(&db, &task_id)?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::truncate_for_task_list;

    #[test]
    fn test_truncate_for_task_list_keeps_short_content() {
        let input = Some("short".to_string());
        let output = truncate_for_task_list(input, 10);
        assert_eq!(output.as_deref(), Some("short"));
    }

    #[test]
    fn test_truncate_for_task_list_truncates_long_content() {
        let input = Some("x".repeat(20));
        let output = truncate_for_task_list(input, 10).expect("content should exist");
        assert!(output.starts_with("xxxxxxxxxx"));
        assert!(output.contains("contenu tronque"));
    }

    #[test]
    fn test_truncate_for_task_list_respects_utf8_boundaries() {
        let input = Some("éééé".to_string());
        let output = truncate_for_task_list(input, 3).expect("content should exist");
        assert!(output.starts_with("é"));
        assert!(!output.starts_with("éé"));
    }
}
