use crate::core::engine::shell;
use crate::core::fleet;
use crate::core::project::Project;
use crate::core::server::{resolve_context, SshClient, SshResolveArgs};
use serde::Serialize;

#[derive(Debug, Default, Clone, Serialize)]
pub struct FleetExecProjectResult {
    pub project_id: String,
    pub server_id: Option<String>,
    pub base_path: Option<String>,
    pub command: String,
    pub status: String,
    pub stdout: Option<String>,
    pub stderr: Option<String>,
    pub exit_code: Option<i32>,
    pub error: Option<String>,
}

#[derive(Debug, Default, Clone, Serialize)]
pub struct FleetExecSummary {
    pub total: u32,
    pub succeeded: u32,
    pub failed: u32,
    pub skipped: u32,
}

pub fn collect_exec(
    fleet_id: &str,
    command: Vec<String>,
    check: bool,
    user_override: Option<String>,
) -> crate::core::Result<(Vec<FleetExecProjectResult>, FleetExecSummary, i32)> {
    if command.is_empty() {
        return Err(
            crate::core::Error::validation_missing_argument(vec!["command".to_string()])
                .with_hint("Usage: homeboy fleet exec <fleet> -- <command>".to_string()),
        );
    }

    let command_string = if command.len() == 1 {
        command[0].clone()
    } else {
        shell::quote_args(&command)
    };

    let projects = fleet::get_projects(fleet_id)?;

    if projects.is_empty() {
        return Err(crate::core::Error::validation_invalid_argument(
            "fleet",
            "Fleet has no projects",
            Some(fleet_id.to_string()),
            None,
        ));
    }

    let mut results: Vec<FleetExecProjectResult> = Vec::new();
    let mut summary = FleetExecSummary {
        total: projects.len() as u32,
        ..Default::default()
    };

    for proj in &projects {
        let server_id = proj.server_id.clone();

        if check {
            let effective_cmd = planned_command(proj, &command_string);
            results.push(FleetExecProjectResult {
                project_id: proj.id.clone(),
                server_id: server_id.clone(),
                base_path: proj.base_path.clone(),
                command: effective_cmd,
                status: "planned".to_string(),
                ..Default::default()
            });
            continue;
        }

        let resolve_result = match resolve_context(&SshResolveArgs {
            id: None,
            project: Some(proj.id.clone()),
            server: None,
        }) {
            Ok(r) => r,
            Err(e) => {
                record_failed_project_result(
                    &mut results,
                    &mut summary,
                    proj,
                    &server_id,
                    &command_string,
                    &e,
                );
                continue;
            }
        };

        let mut client =
            match SshClient::from_server(&resolve_result.server, &resolve_result.server_id) {
                Ok(c) => c,
                Err(e) => {
                    record_failed_project_result(
                        &mut results,
                        &mut summary,
                        proj,
                        &server_id,
                        &command_string,
                        &e,
                    );
                    continue;
                }
            };

        if let Some(ref user) = user_override {
            client.user = user.clone();
        }

        let effective_cmd = match &resolve_result.base_path {
            Some(bp) => format!("cd {} && {}", shell::quote_path(bp), &command_string),
            None => command_string.clone(),
        };

        let output = client.execute(&effective_cmd);

        if output.success {
            summary.succeeded += 1;
        } else {
            summary.failed += 1;
        }

        results.push(FleetExecProjectResult {
            project_id: proj.id.clone(),
            server_id: server_id.clone(),
            base_path: proj.base_path.clone(),
            command: effective_cmd,
            status: if output.success {
                "success".to_string()
            } else {
                "failed".to_string()
            },
            stdout: Some(output.stdout),
            stderr: Some(output.stderr),
            exit_code: Some(output.exit_code),
            error: None,
        });
    }

    if check {
        summary.skipped = summary.total;
    }

    let exit_code = if summary.failed > 0 { 1 } else { 0 };
    Ok((results, summary, exit_code))
}

fn planned_command(project: &Project, command_string: &str) -> String {
    match &project.base_path {
        Some(bp) => format!("cd {} && {}", shell::quote_path(bp), command_string),
        None => command_string.to_string(),
    }
}

fn failed_project_result(
    project: &Project,
    server_id: &Option<String>,
    command: &str,
    error: &crate::core::Error,
) -> FleetExecProjectResult {
    FleetExecProjectResult {
        project_id: project.id.clone(),
        server_id: server_id.clone(),
        base_path: project.base_path.clone(),
        command: command.to_string(),
        status: "failed".to_string(),
        error: Some(error.to_string()),
        ..Default::default()
    }
}

fn record_failed_project_result(
    results: &mut Vec<FleetExecProjectResult>,
    summary: &mut FleetExecSummary,
    project: &Project,
    server_id: &Option<String>,
    command: &str,
    error: &crate::core::Error,
) {
    summary.failed += 1;
    results.push(failed_project_result(project, server_id, command, error));
}
