use std::env;
use std::fs;
use std::io::{BufRead, Write};
use std::path;
use std::process;

use rand::RngExt;

use crate::error::{AppError, AppErrorKind, AppResult};
use crate::types::state::remove_external_preview;
use crate::types::state::AppState;
use crate::ui::AppBackend;
use crate::utils::format::clear_screen;
use crate::utils::process::wait_for_enter;

use super::reload;

const ENV_TMP_DIR: &str = "TMP_DIR";
const ENV_EDITOR: &str = "EDITOR";
const FILE_PREFIX: &str = "joshuto-";
const RAND_STR_LEN: usize = 10;

/// Writes the selected entries' names to a temp file, opens `$EDITOR` on it, and applies any
/// line-for-line renames the user made, after a confirmation prompt.
pub fn _bulk_rename(app_state: &mut AppState) -> AppResult {
    let tmp_directory = env::var(ENV_TMP_DIR).unwrap_or_else(|_| "/tmp".to_string());

    let editor = std::env::var(ENV_EDITOR)?;

    /* generate a random file name to write to */
    let mut rand_str = String::with_capacity(FILE_PREFIX.len() + RAND_STR_LEN);
    rand_str.push_str(FILE_PREFIX);
    rand::rng()
        .sample_iter(&rand::distr::Alphanumeric)
        .take(RAND_STR_LEN)
        .for_each(|ch| rand_str.push(ch as char));

    /* create this file in a temporary folder */
    let mut file_path = path::PathBuf::from(&tmp_directory);
    file_path.push(rand_str);

    let entries = app_state
        .state
        .tab_state_ref()
        .curr_tab_ref()
        .curr_list_ref()
        .map_or(vec![], |s| s.selected_or_current());

    /* write file names into temporary file to edit */
    {
        let mut file = fs::File::create(&file_path)?;
        for path in entries.iter() {
            let file_name = path.file_name();
            let file_name_as_bytes = file_name.as_bytes();
            file.write_all(file_name_as_bytes)?;
            file.write_all(b"\n")?;
        }
    }

    /* open file with text editor to edit */
    {
        let initial_modified = fs::metadata(&file_path)?.modified()?;

        process::Command::new(editor)
            .arg(&file_path)
            .spawn()?
            .wait()?;

        // check if the file was modified since it was created
        let last_modified = fs::metadata(&file_path)?.modified()?;
        if last_modified <= initial_modified {
            // remember to remove file
            std::fs::remove_file(&file_path)?;
            return Ok(());
        }
    }

    let mut paths_renamed: Vec<path::PathBuf> = Vec::with_capacity(entries.len());
    {
        let file = std::fs::File::open(&file_path)?;

        let reader = std::io::BufReader::new(file);
        for line in reader.lines() {
            let line2 = line?;
            let line = line2.trim();
            if line.is_empty() {
                continue;
            }
            let path = path::PathBuf::from(line);
            paths_renamed.push(path);
        }
        std::fs::remove_file(&file_path)?;
    }
    if paths_renamed.len() < entries.len() {
        return Err(AppError::new(
            AppErrorKind::Io,
            "Insufficient inputs".to_string(),
        ));
    }

    clear_screen();
    for (p, q) in entries.iter().zip(paths_renamed.iter()) {
        println!("{:?} -> {:?}", p.file_name(), q);
    }
    print!("Continue with rename? (Y/n): ");
    std::io::stdout().flush()?;

    let mut user_input = String::with_capacity(4);
    std::io::stdin().read_line(&mut user_input)?;

    let user_input_fmt = user_input.trim().to_lowercase();
    match user_input_fmt.as_str() {
        "" | "y" | "yes" => {
            for (p, q) in entries.iter().zip(paths_renamed.iter()) {
                let mut handle = process::Command::new("mv")
                    .arg("-iv")
                    .arg("--")
                    .arg(p.file_name())
                    .arg(q)
                    .spawn()?;
                handle.wait()?;
            }
        }
        _ => {}
    }
    wait_for_enter()?;

    std::fs::remove_file(file_path)?;
    Ok(())
}

/// Implements `bulk_rename`: releases the terminal for `$EDITOR`, runs the bulk rename, then
/// restores the terminal and reloads the current tab.
pub fn bulk_rename(app_state: &mut AppState, backend: &mut AppBackend) -> AppResult {
    remove_external_preview(app_state);
    backend.terminal_drop();
    let res = _bulk_rename(app_state);
    backend.terminal_restore()?;
    reload::soft_reload_curr_tab(app_state)?;
    res
}
