use std::collections::HashMap;
use std::os::unix::process::CommandExt;
use std::path::PathBuf;

use anyhow::{Context, Result, bail};
use dialoguer::Select;

use crate::api;
use crate::util::{K8sClusterStatusExt, sort_k8s_clusters_by_priority};
use mithril_client::models::KubernetesClusterModel;
use mithril_client::models::kubernetes_cluster_model::Status as K8sClusterStatus;

pub async fn run(
    cluster_name: Option<String>,
    show: bool,
    identity: Option<String>,
    command: Vec<String>,
) -> Result<()> {
    let client = api::Client::load()?;

    let cluster = if let Some(name_or_fid) = cluster_name {
        if name_or_fid.starts_with("clust_") {
            client.fetch_k8s_cluster(&name_or_fid).await?
        } else {
            resolve_cluster_by_name(&client, &name_or_fid).await?
        }
    } else {
        select_cluster_interactive(&client).await?
    };

    let kube_host = cluster
        .kube_host
        .as_ref()
        .filter(|h| !h.is_empty())
        .ok_or_else(|| {
            anyhow::anyhow!(
                "Cluster '{}' does not have a kube_host yet. It may still be provisioning.",
                cluster.name
            )
        })?;

    let ssh_target = format!("ubuntu@{kube_host}");

    // Determine identity file
    let identity_path = if let Some(ref path) = identity {
        Some(PathBuf::from(path))
    } else {
        find_ssh_key(&client, &cluster.project, &cluster.ssh_keys).await?
    };

    // Build SSH command
    let mut ssh_args = vec![];
    if let Some(ref path) = identity_path {
        ssh_args.push("-i".to_string());
        ssh_args.push(path.display().to_string());
    }
    ssh_args.push(ssh_target.clone());
    ssh_args.extend(command.clone());

    if show {
        println!("ssh {}", ssh_args.join(" "));
        return Ok(());
    }

    if command.is_empty() {
        println!(
            "Connecting to cluster '{}' at {}...",
            cluster.name, kube_host
        );
    } else {
        println!(
            "Running command on cluster '{}': {}",
            cluster.name,
            command.join(" ")
        );
    }

    let mut cmd = std::process::Command::new("ssh");
    cmd.args(&ssh_args);
    let err = cmd.exec();
    bail!("Failed to execute ssh: {err}");
}

async fn resolve_cluster_by_name(
    client: &api::Client,
    name: &str,
) -> Result<KubernetesClusterModel> {
    let projects = client.fetch_projects().await?;

    let mut all_clusters = Vec::new();
    for project in &projects {
        let clusters = client.fetch_k8s_clusters(&project.fid).await?;
        all_clusters.extend(clusters);
    }

    let matches: Vec<_> = all_clusters
        .into_iter()
        .filter(|c| c.name == name)
        .collect();

    match matches.len() {
        0 => bail!("Cluster '{name}' not found"),
        1 => Ok(matches.into_iter().next().unwrap()),
        _ => {
            println!("Found {} clusters with name '{}':", matches.len(), name);
            let selection = Select::new()
                .with_prompt("Select cluster")
                .items(
                    &matches
                        .iter()
                        .map(|c| format!("{} ({}, {})", c.name, c.region, c.fid))
                        .collect::<Vec<_>>(),
                )
                .default(0)
                .interact()
                .context("Failed to get user selection")?;
            Ok(matches.into_iter().nth(selection).unwrap())
        }
    }
}

async fn select_cluster_interactive(client: &api::Client) -> Result<KubernetesClusterModel> {
    println!("Fetching clusters...");

    let projects = client.fetch_projects().await?;
    if projects.is_empty() {
        bail!("No projects found");
    }

    let project_names: HashMap<&str, &str> = projects
        .iter()
        .map(|p| (p.fid.as_str(), p.name.as_str()))
        .collect();

    let mut all_clusters: Vec<KubernetesClusterModel> = Vec::new();
    for project in &projects {
        let clusters = client.fetch_k8s_clusters(&project.fid).await?;
        all_clusters.extend(clusters);
    }

    all_clusters.retain(|c| c.status != K8sClusterStatus::Terminated);

    if all_clusters.is_empty() {
        bail!("No active clusters found");
    }

    sort_k8s_clusters_by_priority(&mut all_clusters);

    let items: Vec<String> = all_clusters
        .iter()
        .map(|c| {
            let proj_name = project_names.get(c.project.as_str()).unwrap_or(&"?");
            format!(
                "{} | {} | {} | {} | {}",
                c.name,
                proj_name,
                c.region,
                c.status.as_str(),
                c.kube_host.as_deref().unwrap_or("-")
            )
        })
        .collect();

    let selection = Select::new()
        .with_prompt("Select cluster to SSH into")
        .items(&items)
        .default(0)
        .interact()
        .context("Failed to get user selection")?;

    Ok(all_clusters.into_iter().nth(selection).unwrap())
}

async fn find_ssh_key(
    client: &api::Client,
    project_fid: &str,
    cluster_ssh_key_fids: &[String],
) -> Result<Option<PathBuf>> {
    if cluster_ssh_key_fids.is_empty() {
        return Ok(None);
    }

    // Fetch SSH key details from the API
    let ssh_keys = client.fetch_ssh_keys(project_fid).await?;

    // Get the names of SSH keys authorized for this cluster
    let authorized_key_names: Vec<&str> = ssh_keys
        .iter()
        .filter(|k| cluster_ssh_key_fids.contains(&k.fid))
        .map(|k| k.name.as_str())
        .collect();

    // Common SSH key locations
    let home = std::env::var("HOME").context("HOME environment variable not set")?;
    let ssh_dir = PathBuf::from(&home).join(".ssh");

    let candidate_files = ["id_ed25519", "id_rsa", "id_ecdsa", "id_dsa"];

    // Try to find a key whose filename matches one of the authorized key names
    for key_name in &authorized_key_names {
        let key_path = ssh_dir.join(key_name);
        if key_path.exists() {
            return Ok(Some(key_path));
        }
        // Also try with common extensions
        let key_path_with_ext = ssh_dir.join(format!("{key_name}.pem"));
        if key_path_with_ext.exists() {
            return Ok(Some(key_path_with_ext));
        }
    }

    // Fall back to default key files
    for candidate in &candidate_files {
        let key_path = ssh_dir.join(candidate);
        if key_path.exists() {
            return Ok(Some(key_path));
        }
    }

    // No key found, SSH will use its default behavior
    Ok(None)
}
