bendis 0.5.1

A patch tool for Bender to work better in HERIS project
use anyhow::{bail, Context, Result};
use colored::Colorize;
use std::fs;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::process::{Command, Stdio};

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

pub fn run(hard: bool) -> Result<()> {
    // Check for legacy structure and offer migration
    if config::check_and_migrate_if_needed()? {
        // Migration was performed or user declined
        // If migration was successful, continue with update
        // If user declined, exit
        if !config::get_bendis_dir().exists() {
            // User declined migration and no new structure exists
            bail!(
                "error: bendis_workspace directory not found\nrun `bendis init` to migrate or initialize the project"
            );
        }
    }

    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_workspace directory exists
    if !bendis_dir.exists() {
        bail!(
            "error: bendis_workspace directory not found\nrun `bendis init` first to initialize the project"
        );
    }

    // Check bendis_workspace/.gitignore if enabled in config
    let mut gitignore_warning = None;
    if cfg.gitignore_check == 1 {
        if let Err(e) = config::check_bendis_gitignore() {
            gitignore_warning = Some(e.to_string());
        }
    }

    // Step 1: Copy hw/ and target/ directories from root to bendis_workspace/
    println!("  Syncing hw/ and target/ directories to bendis_workspace/...");
    config::copy_root_dirs_to_bendis_workspace()?;

    // Step 2: Run bender update in bendis_workspace directory
    println!("  Preparing cache files (it may take long)...");
    run_bender_update_in_bendis(&bendis_dir, cfg.silent_mode == 1)?;

    // Step 3: Run format converter
    println!("  Converting URLs...");
    let configs_changed = format::convert(&bendis_dir, &root_dir)?;
    let needs_update = configs_changed
        || !root_dir.join("Bender.lock").exists()
        || !root_dir.join(".bender").exists();

    // Step 4: Ensure bendis_workspace/.gitignore has required entries (hw/, target/)
    println!("  Updating bendis_workspace/.gitignore...");
    config::ensure_bendis_workspace_gitignore_entries()?;

    // Step 5: Ensure root .gitignore does not contain hw/, target/
    println!("  Updating root .gitignore...");
    config::ensure_root_gitignore_excludes_hw_target()?;

    // Step 6: Check if files changed, run bender update if needed
    println!("  Checking for changes...");
    if needs_update {
        println!("  Detected changes, updating dependencies...");
        cleanup_root_files(&root_dir, hard)?;
        run_bender_update_in_root()?;
    } else {
        println!("  No changes detected, skipping bender update");
    }

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

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

    // Display gitignore warning if there was an issue
    if let Some(warning_msg) = gitignore_warning {
        eprintln!("\n{}", "warning:".yellow().bold());
        eprintln!("  {}", warning_msg);
        eprintln!("  Some files in bendis_workspace/ may be tracked by git unexpectedly.");
        eprintln!("  Please review bendis_workspace/.gitignore and ensure it contains the required entries.");
        eprintln!("  To disable this check, set 'gitignore_check = 0' in: {}",
                  "bendis config".cyan());
    }

    if hard {
        println!("Preparing AegisRTL workspace...");
        let result = hardening::run(&root_dir)?;
        match result.status {
            HardeningStatus::Completed => {
                println!("Hardening completed. Changed RTL files: {}", result.changed_files.len());
            }
            HardeningStatus::Skipped => {
                println!("Hardening skipped: aegisrtl/scripts/harden.sh was not found");
            }
        }
        println!("Hardened RTL dependencies activated");
    }

    println!("Done");

    Ok(())
}

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

    // Always capture output for error logging
    cmd.stdout(Stdio::piped());
    cmd.stderr(Stdio::piped());

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

    // Capture stdout and stderr
    let stdout = child.stdout.take().unwrap();
    let stderr = child.stderr.take().unwrap();

    let mut stdout_reader = BufReader::new(stdout);
    let mut stderr_reader = BufReader::new(stderr);

    let mut log_buffer = Vec::new();
    let mut line = String::new();

    // Read stdout
    while stdout_reader.read_line(&mut line).unwrap_or(0) > 0 {
        if !silent {
            print!("{}", line);
        }
        log_buffer.push(line.clone());
        line.clear();
    }

    // Read stderr
    while stderr_reader.read_line(&mut line).unwrap_or(0) > 0 {
        if !silent {
            eprint!("{}", line);
        }
        log_buffer.push(line.clone());
        line.clear();
    }

    let status = child.wait()
        .context("Failed to wait for bender process")?;

    if !status.success() {
        // Always display full log on error, regardless of silent mode
        eprintln!("\n{}", "Error: bender update failed in bendis_workspace/".red());
        eprintln!("\nCommand output:");
        for log_line in &log_buffer {
            eprint!("{}", log_line);
        }

        // Check for specific fetch/version error pattern
        let has_fetch_error = log_buffer.iter().any(|line| {
            line.contains("Fetching Dependency") &&
            line.contains("cannot satisfy requirement") &&
            line.contains("may need fetch")
        });

        if has_fetch_error {
            eprintln!("\n{}", "Possible solution:".yellow());
            eprintln!("  1. Delete {} directory", "bendis_workspace/.bender/".cyan());
            eprintln!("  2. Check your internet connection");
            eprintln!("  3. Run {} again", "bendis update".cyan());
        }

        bail!("error: bender update in bendis_workspace/ 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_workspace/Bender.lock");
    }

    Ok(())
}

fn cleanup_root_files(root_dir: &Path, preserve_bender_cache: bool) -> 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() && !preserve_bender_cache {
        fs::remove_dir_all(&old_bender_dir)
            .context("Failed to remove old .bender/ directory")?;
    }

    Ok(())
}

fn run_bender_update_in_root() -> Result<()> {
    let mut cmd = Command::new("bender");
    cmd.arg("update");

    // Always capture output for error logging
    cmd.stdout(Stdio::piped());
    cmd.stderr(Stdio::piped());

    let mut child = cmd.spawn()
        .context("Failed to run bender in root directory")?;

    // Capture stdout and stderr
    let stdout = child.stdout.take().unwrap();
    let stderr = child.stderr.take().unwrap();

    let mut stdout_reader = BufReader::new(stdout);
    let mut stderr_reader = BufReader::new(stderr);

    let mut log_buffer = Vec::new();
    let mut line = String::new();

    // Read stdout (always visible for root bender update)
    while stdout_reader.read_line(&mut line).unwrap_or(0) > 0 {
        print!("{}", line);
        log_buffer.push(line.clone());
        line.clear();
    }

    // Read stderr (always visible for root bender update)
    while stderr_reader.read_line(&mut line).unwrap_or(0) > 0 {
        eprint!("{}", line);
        log_buffer.push(line.clone());
        line.clear();
    }

    let status = child.wait()
        .context("Failed to wait for bender process")?;

    if !status.success() {
        // Display simple error message with full log
        eprintln!("\n{}", "Error: bender update failed in root directory".red());
        eprintln!("\nCommand output:");
        for log_line in &log_buffer {
            eprint!("{}", log_line);
        }

        // Check for specific fetch/version error pattern
        let has_fetch_error = log_buffer.iter().any(|line| {
            line.contains("Fetching Dependency") &&
            line.contains("cannot satisfy requirement") &&
            line.contains("may need fetch")
        });

        if has_fetch_error {
            eprintln!("\n{}", "Possible solution:".yellow());
            eprintln!("  1. Delete {} directory", ".bender/".cyan());
            eprintln!("  2. Check your internet connection");
            eprintln!("  3. Run {} again", "bendis update".cyan());
        }

        bail!("error: bender update in root directory failed");
    }

    Ok(())
}

fn cleanup_bendis_workspace_bender_dir(bendis_dir: &Path, full_cleanup: bool) -> Result<()> {
    if full_cleanup {
        // storage_saving_mode = 1: delete entire bendis_workspace/.bender/
        let bender_dir = bendis_dir.join(".bender");
        if bender_dir.exists() {
            fs::remove_dir_all(&bender_dir)
                .context("Failed to remove bendis_workspace/.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(())
}