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::services::agent_runtime;
use crate::services::process_registry::ProcessRegistry;
use rusqlite::Connection;
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Emitter};
use tokio::time::{interval, Duration};

fn build_task_prompt(task: &AgentTask, project: &Project) -> String {
    format!(
        r#"Tu es un agent d'exécution de tâches pour un projet logiciel.

## Projet
- Nom: {project_name}
- Chemin repo: {project_path}
- Branche de base: {base_branch}

## Tâche
- Titre: {task_title}
- Description:
{task_description}

## Attendu
1. Exécute la tâche dans le contexte du dépôt.
2. Fais des changements concrets si nécessaire.
3. Donne un rapport final en markdown:
   - Résumé
   - Changements
   - Vérifications faites
   - Risques / suites
"#,
        project_name = project.name,
        project_path = project.path,
        base_branch = project.base_branch,
        task_title = task.title,
        task_description = task.description,
    )
}

fn emit_status(
    app_handle: &AppHandle,
    project_id: &str,
    task_id: &str,
    status: &str,
    error: Option<&str>,
) {
    let _ = app_handle.emit(
        "agent-task-updated",
        serde_json::json!({
            "project_id": project_id,
            "task_id": task_id,
            "status": status,
            "error": error,
        }),
    );
}

async fn process_next_task(
    db: &Arc<Mutex<Connection>>,
    app_handle: &AppHandle,
    process_registry: &ProcessRegistry,
) -> Result<bool, String> {
    let next_task = {
        let conn = db.lock().map_err(|e| format!("DB lock failed: {}", e))?;
        let pending = AgentTask::list_pending(&conn)
            .map_err(|e| format!("list pending tasks failed: {}", e))?;

        let mut selected = None;
        for task in pending {
            let enabled =
                ProjectModule::is_enabled(&conn, &task.project_id, MODULE_AGENT_TASK_RUNNER)
                    .map_err(|e| format!("task module lookup failed: {}", e))?;
            if !enabled {
                continue;
            }

            let agent = Agent::get_by_id(&conn, &task.agent_id)
                .map_err(|e| format!("agent lookup failed: {}", e))?;

            if agent.is_runtime_exhausted() {
                if agent.exhaustion_has_expired() {
                    Agent::mark_available(&conn, &agent.id)
                        .map_err(|e| format!("agent availability reset failed: {}", e))?;
                } else {
                    continue;
                }
            }

            selected = Some((task, agent));
            break;
        }

        selected
    };

    let (task, agent) = match next_task {
        Some(payload) => payload,
        None => return Ok(false),
    };

    let project = {
        let conn = db.lock().map_err(|e| format!("DB lock failed: {}", e))?;

        AgentTask::mark_running(&conn, &task.id)
            .map_err(|e| format!("mark task running failed: {}", e))?;
        let project = Project::get_by_id(&conn, &task.project_id)
            .map_err(|e| format!("project lookup failed: {}", e))?;

        project
    };

    emit_status(app_handle, &task.project_id, &task.id, "running", None);

    let prompt = build_task_prompt(&task, &project);
    let args = agent.tool.to_non_interactive_args();
    let result = agent_runtime::run_agent_command_for_task(
        agent.tool.to_command(),
        &args,
        &prompt,
        &project.path,
        900,
        process_registry,
        &task.id,
    )
    .await;

    match result {
        Ok(report) => {
            let current = {
                let conn = db.lock().map_err(|e| format!("DB lock failed: {}", e))?;
                AgentTask::get_by_id(&conn, &task.id)
                    .map_err(|e| format!("task lookup failed: {}", e))?
            };
            if current.status == "cancelled" {
                emit_status(app_handle, &task.project_id, &task.id, "cancelled", None);
                return Ok(true);
            }

            let conn = db.lock().map_err(|e| format!("DB lock failed: {}", e))?;
            AgentTask::mark_done(&conn, &task.id, &report)
                .map_err(|e| format!("mark task done failed: {}", e))?;
            emit_status(app_handle, &task.project_id, &task.id, "done", None);
        }
        Err(error) => {
            let current = {
                let conn = db.lock().map_err(|e| format!("DB lock failed: {}", e))?;
                AgentTask::get_by_id(&conn, &task.id)
                    .map_err(|e| format!("task lookup failed: {}", e))?
            };
            if current.status == "cancelled" {
                emit_status(app_handle, &task.project_id, &task.id, "cancelled", None);
                return Ok(true);
            }

            if let Some(exhaustion) = agent_runtime::detect_agent_exhaustion(&error) {
                let exhausted_until = exhaustion.exhausted_until_rfc3339();
                let waiting_note = format!(
                    "Agent '{}' est épuisé (quota/token). Tâche remise en attente jusqu'à {}.",
                    agent.name, exhausted_until
                );

                let conn = db.lock().map_err(|e| format!("DB lock failed: {}", e))?;
                Agent::mark_exhausted(&conn, &agent.id, &exhaustion.reason, Some(&exhausted_until))
                    .map_err(|e| format!("mark agent exhausted failed: {}", e))?;
                AgentTask::mark_pending(&conn, &task.id, Some(&waiting_note))
                    .map_err(|e| format!("requeue task failed: {}", e))?;
                emit_status(
                    app_handle,
                    &task.project_id,
                    &task.id,
                    "pending",
                    Some(&waiting_note),
                );
                return Ok(true);
            }

            let conn = db.lock().map_err(|e| format!("DB lock failed: {}", e))?;
            AgentTask::mark_error(&conn, &task.id, &error)
                .map_err(|e| format!("mark task error failed: {}", e))?;
            emit_status(
                app_handle,
                &task.project_id,
                &task.id,
                "error",
                Some(&error),
            );
        }
    }

    Ok(true)
}

pub fn start(db: Arc<Mutex<Connection>>, app_handle: AppHandle, process_registry: ProcessRegistry) {
    tauri::async_runtime::spawn(async move {
        let mut tick = interval(Duration::from_secs(8));
        loop {
            tick.tick().await;
            match process_next_task(&db, &app_handle, &process_registry).await {
                Ok(true) => continue,
                Ok(false) => {}
                Err(e) => {
                    eprintln!("task_runner: {}", e);
                }
            }
        }
    });
}
