use crate::api::ApiClient;
use crate::cache::{Cache, CacheMetadata};
use crate::error::Result;
use crate::signal::check_cancelled;
use crate::tap::TapManager;
use crate::ui::create_spinner;
use console::style;
use tracing::instrument;

#[instrument(skip(api_client, cache))]
pub async fn update(api_client: &ApiClient, cache: &Cache) -> Result<()> {
    let spinner = create_spinner("Updating package index...");

    let start = std::time::Instant::now();

    let metadata = cache.load_metadata().await?;

    let (formulae_etag, formulae_last_modified) = metadata
        .as_ref()
        .map(|m| {
            (
                m.formulae_etag.as_deref(),
                m.formulae_last_modified.as_deref(),
            )
        })
        .unwrap_or((None, None));

    let (casks_etag, casks_last_modified) = metadata
        .as_ref()
        .map(|m| (m.casks_etag.as_deref(), m.casks_last_modified.as_deref()))
        .unwrap_or((None, None));

    let (formulae_result, casks_result) = tokio::join!(
        api_client.fetch_formulae_conditional(formulae_etag, formulae_last_modified),
        api_client.fetch_casks_conditional(casks_etag, casks_last_modified)
    );

    let formulae_fetch = formulae_result?;
    let casks_fetch = casks_result?;

    let (_formulae, formula_count) = if formulae_fetch.not_modified {
        let cached = cache.load_formulae().await?;
        let count = cached.len();
        (cached, count)
    } else if let Some(data) = formulae_fetch.data {
        let count = data.len();
        cache.save_formulae(&data).await?;
        (data, count)
    } else {
        let cached = cache.load_formulae().await?;
        let count = cached.len();
        (cached, count)
    };

    let (_casks, cask_count) = if casks_fetch.not_modified {
        let cached = cache.load_casks().await?;
        let count = cached.len();
        (cached, count)
    } else if let Some(data) = casks_fetch.data {
        let count = data.len();
        cache.save_casks(&data).await?;
        (data, count)
    } else {
        let cached = cache.load_casks().await?;
        let count = cached.len();
        (cached, count)
    };

    let mut tap_manager = TapManager::new()?;
    tap_manager.load().await?;
    let taps = tap_manager
        .list_taps()
        .iter()
        .map(|t| t.full_name.clone())
        .collect::<Vec<_>>();
    let tap_count = taps.len();

    if tap_count > 0 {
        cache.invalidate_all_tap_caches().await?;

        for tap_name in &taps {
            check_cancelled()?;
            if let Err(e) = tap_manager.update_tap(tap_name).await {
                eprintln!(
                    "  {} failed to update tap {}: {}",
                    style("!").yellow(),
                    style(tap_name).magenta(),
                    e
                );
            }
        }
    }

    let new_metadata = CacheMetadata {
        last_updated: std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs() as i64,
        formula_count,
        cask_count,
        formulae_etag: formulae_fetch
            .etag
            .or_else(|| metadata.as_ref().and_then(|m| m.formulae_etag.clone())),
        formulae_last_modified: formulae_fetch.last_modified.or_else(|| {
            metadata
                .as_ref()
                .and_then(|m| m.formulae_last_modified.clone())
        }),
        casks_etag: casks_fetch
            .etag
            .or_else(|| metadata.as_ref().and_then(|m| m.casks_etag.clone())),
        casks_last_modified: casks_fetch.last_modified.or_else(|| {
            metadata
                .as_ref()
                .and_then(|m| m.casks_last_modified.clone())
        }),
    };
    cache.save_metadata(&new_metadata).await?;

    spinner.finish_and_clear();

    let elapsed = start.elapsed();
    let core_status = if formulae_fetch.not_modified && casks_fetch.not_modified {
        "up to date"
    } else if formulae_fetch.not_modified {
        "updated casks"
    } else if casks_fetch.not_modified {
        "updated formulae"
    } else {
        "updated"
    };

    if tap_count > 0 {
        println!(
            "{} {} · {} formulae, {} casks, {} {}{}",
            style("✓").green(),
            core_status,
            style(formula_count).cyan(),
            style(cask_count).cyan(),
            style(tap_count).cyan(),
            if tap_count == 1 { "tap" } else { "taps" },
            crate::timing::elapsed_suffix(elapsed)
        );
    } else {
        println!(
            "{} {} · {} formulae, {} casks{}",
            style("✓").green(),
            core_status,
            style(formula_count).cyan(),
            style(cask_count).cyan(),
            crate::timing::elapsed_suffix(elapsed)
        );
    }

    Ok(())
}
