bendis 0.2.2

A patch tool for Bender to work better in HERIS project
use anyhow::{bail, Context, Result};
use sha2::{Sha256, Digest};
use std::fs;
use std::path::Path;
use std::process::{Command, Stdio};

use crate::converter::format;
use crate::utils::config::{self, BendisConfig};

pub fn run() -> Result<()> {
    println!("Updating dependencies...");

    // Load configuration
    let cfg = BendisConfig::load()?;

    let bendis_dir = config::get_bendis_dir();
    let root_dir = config::get_root_dir();

    // Check if .bendis directory exists
    if !bendis_dir.exists() {
        bail!(
            "error: .bendis directory not found\nrun `bendis init` first to initialize the project"
        );
    }

    // Step 1: Run bender update in .bendis directory
    println!("  Preparing cache files...");
    run_bender_update_in_bendis(&bendis_dir, cfg.silent_mode == 1)?;

    // Step 2: Run format converter
    println!("  Converting URLs...");
    format::convert(&bendis_dir, &root_dir)?;

    // Step 3: Check if files changed, run bender update if needed
    println!("  Checking for changes...");
    let needs_update = check_files_changed(&root_dir)?;

    if needs_update {
        println!("  Detected changes, updating dependencies...");
        cleanup_root_files(&root_dir)?;
        run_bender_update_in_root()?;
    } else {
        println!("  No changes detected, skipping bender update");
    }

    // Step 4: Clean up based on storage_saving_mode
    println!("  Cleaning up cache...");
    cleanup_bendis_bender_dir(&bendis_dir, cfg.storage_saving_mode == 1)?;

    // Step 5: Verify
    verify_completion(&root_dir)?;

    println!("Done");

    Ok(())
}

fn calculate_file_hash(path: &Path) -> Result<String> {
    let content = fs::read(path)
        .with_context(|| format!("Failed to read {}", path.display()))?;
    let hash = Sha256::digest(&content);
    Ok(format!("{:x}", hash))
}

fn check_files_changed(root_dir: &Path) -> Result<bool> {
    let bender_yml = root_dir.join("Bender.yml");
    let dot_bender_yml = root_dir.join(".bender.yml");

    // If files don't exist, we need to update
    if !bender_yml.exists() || !dot_bender_yml.exists() {
        return Ok(true);
    }

    // Calculate current hashes
    let current_bender_yml_hash = calculate_file_hash(&bender_yml)?;
    let current_dot_bender_yml_hash = calculate_file_hash(&dot_bender_yml)?;

    // Calculate new hashes from .bendis
    let bendis_dir = config::get_bendis_dir();
    let new_bender_yml = bendis_dir.join("Bender.yml");
    let new_dot_bender_yml = root_dir.join(".bender.yml"); // This was just written by format::convert

    if !new_bender_yml.exists() {
        return Ok(true);
    }

    let new_bender_yml_hash = calculate_file_hash(&new_bender_yml)?;
    let new_dot_bender_yml_hash = calculate_file_hash(&new_dot_bender_yml)?;

    // Compare hashes
    Ok(current_bender_yml_hash != new_bender_yml_hash ||
       current_dot_bender_yml_hash != new_dot_bender_yml_hash)
}

fn run_bender_update_in_bendis(bendis_dir: &Path, silent: bool) -> Result<()> {
    let mut cmd = Command::new("bender");
    cmd.args(&["-d", "./.bendis", "update"]);

    // If silent mode is enabled, suppress all output
    if silent {
        cmd.stdout(Stdio::null());
        cmd.stderr(Stdio::null());
    }

    let status = cmd.status()
        .context("Failed to run bender. Is bender installed and in PATH?")?;

    if !status.success() {
        bail!("error: bender update in .bendis/ failed");
    }

    // Check if Bender.lock was created
    let lock_file = bendis_dir.join("Bender.lock");
    if !lock_file.exists() {
        bail!("error: failed to generate .bendis/Bender.lock");
    }

    Ok(())
}

fn cleanup_root_files(root_dir: &Path) -> Result<()> {
    // Remove old Bender.lock if exists
    let old_lock = root_dir.join("Bender.lock");
    if old_lock.exists() {
        fs::remove_file(&old_lock)
            .context("Failed to remove old Bender.lock")?;
    }

    // Remove old .bender/ directory if exists
    let old_bender_dir = root_dir.join(".bender");
    if old_bender_dir.exists() {
        fs::remove_dir_all(&old_bender_dir)
            .context("Failed to remove old .bender/ directory")?;
    }

    Ok(())
}

fn run_bender_update_in_root() -> Result<()> {
    let status = Command::new("bender")
        .arg("update")
        .status()
        .context("Failed to run bender in root directory")?;

    if !status.success() {
        bail!("error: bender update in root directory failed");
    }

    Ok(())
}

fn cleanup_bendis_bender_dir(bendis_dir: &Path, full_cleanup: bool) -> Result<()> {
    if full_cleanup {
        // storage_saving_mode = 1: delete entire .bendis/.bender/
        let bender_dir = bendis_dir.join(".bender");
        if bender_dir.exists() {
            fs::remove_dir_all(&bender_dir)
                .context("Failed to remove .bendis/.bender/")?;
        }
    } else {
        // storage_saving_mode = 0: don't delete anything (keep cache)
        // Do nothing
    }

    Ok(())
}

fn verify_completion(root_dir: &Path) -> Result<()> {
    let mut missing = Vec::new();

    // Check required files
    if !root_dir.join("Bender.yml").exists() {
        missing.push("Bender.yml");
    }
    if !root_dir.join(".bender.yml").exists() {
        missing.push(".bender.yml");
    }
    if !root_dir.join("Bender.lock").exists() {
        missing.push("Bender.lock");
    }
    if !root_dir.join(".bender").exists() {
        missing.push(".bender/");
    }

    if !missing.is_empty() {
        bail!("error: verification failed, missing: {}", missing.join(", "));
    }

    Ok(())
}