use anyhow::{Context, Result};
use dialoguer::Select;
use tabled::{Table, Tabled, settings::Style};

use crate::api;
use crate::util::{BidStatusExt, calculate_age, sort_bids_by_priority};

#[derive(Tabled)]
struct InstanceRow {
    #[tabled(rename = "#")]
    index: usize,
    #[tabled(rename = "Name")]
    name: String,
    #[tabled(rename = "Status")]
    status: String,
    #[tabled(rename = "SSH")]
    ssh: String,
    #[tabled(rename = "Private IP")]
    private_ip: String,
}

pub async fn run(bid_name: Option<String>, json: bool) -> Result<()> {
    let client = api::Client::load()?;

    let (bid, _project_fid) = if let Some(name) = bid_name {
        find_bid_by_name(&client, &name).await?
    } else {
        select_bid_interactive(&client).await?
    };

    // Fetch instances for this bid
    let instances = client.fetch_instances_for_project(&bid.project).await?;
    let bid_instances: Vec<_> = instances
        .into_iter()
        .filter(|(inst, _)| bid.instances.contains(&inst.fid))
        .collect();

    if json {
        let output = serde_json::json!({
            "bid": {
                "fid": bid.fid,
                "name": bid.name,
                "project": bid.project,
                "instance_type": bid.instance_type,
                "region": bid.region,
                "status": bid.status.as_str(),
                "limit_price": bid.limit_price,
                "instance_quantity": bid.instance_quantity,
                "created_at": bid.created_at,
                "created_by": bid.created_by,
                "deactivated_at": bid.deactivated_at,
            },
            "instances": bid_instances.iter().map(|(inst, _)| serde_json::json!({
                "fid": inst.fid,
                "name": inst.name,
                "status": format!("{:?}", inst.status),
                "ssh_destination": inst.ssh_destination,
                "private_ip": inst.private_ip,
                "region": inst.region,
                "created_at": inst.created_at,
            })).collect::<Vec<_>>()
        });
        println!("{}", serde_json::to_string_pretty(&output)?);
        return Ok(());
    }

    // Display bid details
    println!();
    println!("Bid: {}", bid.name);
    println!("{}", "=".repeat(40));
    println!("FID:            {}", bid.fid);
    println!("Project:        {}", bid.project);
    println!("Status:         {}", bid.status.colorize());
    println!("Instance Type:  {}", bid.instance_type);
    println!("Region:         {}", bid.region.as_deref().unwrap_or("any"));
    println!("Max Price:      ${}/hr", bid.limit_price);
    println!(
        "Instances:      {}/{}",
        bid.instances.len(),
        bid.instance_quantity
    );
    println!(
        "Created:        {} ({})",
        bid.created_at,
        calculate_age(&bid.created_at)
    );
    println!("Created By:     {}", bid.created_by);
    if let Some(ref deactivated) = bid.deactivated_at {
        println!("Deactivated:    {deactivated}");
    }

    // Display instances
    if !bid_instances.is_empty() {
        let rows: Vec<InstanceRow> = bid_instances
            .iter()
            .enumerate()
            .map(|(i, (inst, _))| InstanceRow {
                index: i,
                name: inst.name.clone(),
                status: format!("{:?}", inst.status),
                ssh: inst
                    .ssh_destination
                    .as_ref()
                    .map(|s| format!("ubuntu@{s}"))
                    .unwrap_or_else(|| "-".to_string()),
                private_ip: inst.private_ip.clone().unwrap_or_else(|| "-".to_string()),
            })
            .collect();

        println!();
        println!("Instances:");
        let table = Table::new(rows).with(Style::rounded()).to_string();
        println!("{table}");
    }

    println!();

    Ok(())
}

async fn find_bid_by_name(
    client: &api::Client,
    name: &str,
) -> Result<(mithril_client::models::BidModel, String)> {
    println!("Looking for bid '{name}'...");

    let all_matches = client.search_bids_by_name(name).await?;

    if all_matches.is_empty() {
        anyhow::bail!("Bid '{name}' not found");
    }

    if all_matches.len() == 1 {
        let bid = all_matches.into_iter().next().unwrap();
        let project = bid.project.clone();
        return Ok((bid, project));
    }

    println!("Found {} bids with name '{}':", all_matches.len(), name);
    let selection = Select::new()
        .with_prompt("Select which bid")
        .items(
            &all_matches
                .iter()
                .map(|bid| {
                    format!(
                        "{} (project: {}, region: {}, status: {})",
                        bid.name,
                        bid.project,
                        bid.region.as_deref().unwrap_or("unknown"),
                        bid.status.as_str()
                    )
                })
                .collect::<Vec<_>>(),
        )
        .default(0)
        .interact()
        .context("Failed to get user selection")?;

    let bid = all_matches.into_iter().nth(selection).unwrap();
    let project = bid.project.clone();
    Ok((bid, project))
}

async fn select_bid_interactive(
    client: &api::Client,
) -> Result<(mithril_client::models::BidModel, String)> {
    println!("Fetching bids...");

    let bids = client.fetch_bids(None, None, None).await?;

    if bids.is_empty() {
        anyhow::bail!("No bids found");
    }

    let mut sorted_bids: Vec<_> = bids.into_iter().collect();
    sort_bids_by_priority(&mut sorted_bids);

    let selection = Select::new()
        .with_prompt("Select bid to view")
        .items(
            &sorted_bids
                .iter()
                .map(|bid| {
                    format!(
                        "{} | {} | {} | {} | {}/{}",
                        bid.name,
                        bid.instance_type,
                        bid.region.as_deref().unwrap_or("-"),
                        bid.status.as_str(),
                        bid.instances.len(),
                        bid.instance_quantity
                    )
                })
                .collect::<Vec<_>>(),
        )
        .default(0)
        .interact()
        .context("Failed to get user selection")?;

    let bid = sorted_bids.into_iter().nth(selection).unwrap();
    let project = bid.project.clone();
    Ok((bid, project))
}
