use crate::error::AppError;
use crate::models::agent::{Agent, AgentRole, AgentTool};
use crate::services::agent_runtime;
use crate::AppState;
use rusqlite::params;
use tauri::State;

const PROMPT_IMPROVEMENT_TIMEOUT_SECS: u64 = 300;

fn agent_role_label(role: &AgentRole) -> &'static str {
    match role {
        AgentRole::Analyst => "analyste",
        AgentRole::Developer => "developpeur",
    }
}

fn agent_tool_label(tool: &AgentTool) -> &'static str {
    match tool {
        AgentTool::Codex => "Codex",
        AgentTool::ClaudeCode => "Claude Code",
    }
}

fn build_prompt_improvement_request(
    name: &str,
    role: &AgentRole,
    tool: &AgentTool,
    custom_prompt: &str,
) -> String {
    let trimmed_name = name.trim();
    let normalized_name = if trimmed_name.is_empty() {
        "Agent sans nom"
    } else {
        trimmed_name
    };
    let current_prompt = custom_prompt.trim();
    let current_prompt = if current_prompt.is_empty() {
        "(vide)"
    } else {
        current_prompt
    };

    format!(
        r#"Tu es un expert en prompt engineering pour agents logiciels assistes par LLM.

Tu dois ameliorer le prompt complementaire d'un agent Orchai.
Ce texte sera ajoute a un prompt systeme deja fourni par l'application.

## Contexte agent
- Nom: {name}
- Role: {role}
- Outil cible: {tool}

## Regles
1. Preserve l'intention du prompt existant.
2. Rends les instructions plus claires, plus specifiques et plus facilement executables par un LLM.
3. Evite de dupliquer le contexte deja apporte par le prompt systeme integre.
4. Garde un style concis, direct et oriente action.
5. Si le prompt actuel est vide, propose un prompt complementaire court et utile pour ce role.
6. Reponds uniquement avec le prompt final, sans explication, sans markdown, sans balises de code.

## Prompt actuel
{current_prompt}"#,
        name = normalized_name,
        role = agent_role_label(role),
        tool = agent_tool_label(tool),
        current_prompt = current_prompt,
    )
}

fn normalize_improved_prompt(output: &str) -> String {
    let trimmed = output.trim();
    let lines: Vec<&str> = trimmed.lines().collect();

    if lines.len() >= 3
        && lines
            .first()
            .is_some_and(|line| line.trim_start().starts_with("```"))
        && lines.last().is_some_and(|line| line.trim() == "```")
    {
        return lines[1..lines.len() - 1].join("\n").trim().to_string();
    }

    trimmed.to_string()
}

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

    let agent = Agent::insert(&db, &name, role, tool, &custom_prompt)?;
    Ok(agent)
}

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

    let agents = Agent::list(&db)?;
    Ok(agents)
}

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

    let agent = Agent::get_by_id(&db, &id)?;
    Ok(agent)
}

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

    let previous = Agent::get_by_id(&db, &id)?;
    Agent::update(&db, &id, &name, role.clone(), tool, &custom_prompt)?;

    if previous.role != role {
        match role {
            AgentRole::Analyst => {
                db.execute(
                    "UPDATE watched_trackers
                     SET developer_agent_id = NULL,
                         status = 'invalid'
                     WHERE developer_agent_id = ?1",
                    params![id],
                )?;
            }
            AgentRole::Developer => {
                db.execute(
                    "UPDATE watched_trackers
                     SET analyst_agent_id = NULL,
                         status = 'invalid'
                     WHERE analyst_agent_id = ?1",
                    params![id],
                )?;
            }
        }
    }

    Ok(())
}

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

    Agent::delete(&db, &id)?;
    Ok(())
}

#[tauri::command]
pub async fn improve_agent_prompt(
    name: String,
    role: AgentRole,
    tool: AgentTool,
    custom_prompt: String,
) -> Result<String, AppError> {
    let request = build_prompt_improvement_request(&name, &role, &tool, &custom_prompt);
    let working_dir = std::env::temp_dir().to_string_lossy().to_string();
    let args = tool.to_non_interactive_args();

    let improved = agent_runtime::run_agent_command(
        tool.to_command(),
        &args,
        &request,
        &working_dir,
        PROMPT_IMPROVEMENT_TIMEOUT_SECS,
    )
    .await
    .map_err(AppError::from)?;

    let normalized = normalize_improved_prompt(&improved);
    if normalized.is_empty() {
        return Err(AppError::from(
            "L'agent n'a renvoye aucun prompt exploitable".to_string(),
        ));
    }

    Ok(normalized)
}

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

    #[test]
    fn test_build_prompt_improvement_request_contains_context() {
        let prompt = build_prompt_improvement_request(
            "Analyste Tuleap",
            &AgentRole::Analyst,
            &AgentTool::Codex,
            "Priorise la cause racine.",
        );

        assert!(prompt.contains("Analyste Tuleap"));
        assert!(prompt.contains("analyste"));
        assert!(prompt.contains("Codex"));
        assert!(prompt.contains("Priorise la cause racine."));
    }

    #[test]
    fn test_normalize_improved_prompt_removes_code_fences() {
        let normalized =
            normalize_improved_prompt("```text\nSois precis.\nListe les hypotheses.\n```");

        assert_eq!(normalized, "Sois precis.\nListe les hypotheses.");
    }
}
