use std::path::{Path, PathBuf};

use console::Style;
use cuba_lib::{
    core::cuba::{BackupEntry, BrowseHandle},
    shared::{
        flags::Flags,
        masked_flags::MaskedFlags,
        npath::{Dir, NPath, Rel, UNPath},
    },
};
use inquire::Text;

use crate::autocompleter::StringListAutoCompleter;

/// Prompt browse backup.
pub fn prompt_browse_backup(backup_name: &str, browse_handle: &BrowseHandle) {
    let mut rel_path = PathBuf::new();
    println!("Type 'help' for help");

    loop {
        let current_path_backup_entries: Vec<BackupEntry> =
            get_dir_backup_entries(&rel_path, browse_handle);
        let mut dirs = Vec::<String>::new();

        for backup_entry in &current_path_backup_entries {
            if backup_entry.rel_path.is_dir() {
                dirs.push(backup_entry.name());
            }
        }

        let autocompleter = StringListAutoCompleter::new(dirs.clone());
        let prompt_str = format!("{}:{}>", backup_name, rel_path.to_string_lossy());
        let input = match Text::new(&prompt_str)
            .with_autocomplete(autocompleter)
            .prompt()
        {
            Ok(s) => s,
            Err(_) => return,
        };

        let mut tokens = input.splitn(2, ' ');
        let command = tokens.next().unwrap_or("");
        let argument = tokens.next();

        match command {
            "cd" => {
                if let Some(argument) = argument {
                    if argument == "/" {
                        rel_path.clear();
                    } else if argument == ".." {
                        rel_path.pop();
                    } else {
                        if dirs.contains(&argument.to_string()) {
                            rel_path.push(argument);
                        } else {
                            browse_handle
                                .send_error_str(&format!("No such directory: {}", argument));
                        }
                    }
                }
            }
            "ls" => {
                show_backup_entries(&current_path_backup_entries, false);
            }
            "find" => {
                if let Some(argument) = argument {
                    if argument == "orphans" {
                        let orphans = MaskedFlags::new()
                            .with_flags(Flags::ORPHAN)
                            .with_mask(Flags::ORPHAN);
                        show_backup_entries(&browse_handle.find(&orphans), true);
                    } else if argument == "errors" {
                        let errors = MaskedFlags::new()
                            .with_flags(Flags::VERIFY_ERROR)
                            .with_mask(Flags::VERIFY_ERROR);
                        show_backup_entries(&browse_handle.find(&errors), true);
                    } else {
                        browse_handle.send_error_str(&format!("Invalid argument: {}", argument));
                    }
                }
            }
            "help" => {
                println!("Colors:");
                println!("  yellow = not verified, red = verification error, blue = orphaned");
                println!("Available commands:");
                println!("  cd <dir>        Change directory (use '/' for root, '..' to go up)");
                println!("  ls              List entries in the current directory");
                println!("  find orphans    Find all orphaned entries");
                println!("  find errors     Find all entries with verification errors");
                println!("  help            Show this help message");
                println!("  quit            Close the browse session");
            }
            "quit" => return,
            _ => {
                browse_handle.send_error_str(&format!("Unknown command: {}", command));
            }
        }
    }
}

/// Get the backup entries of a dir.
fn get_dir_backup_entries(rel_path: &Path, browse_handle: &BrowseHandle) -> Vec<BackupEntry> {
    let mut backup_entries = Vec::<BackupEntry>::new();

    if let Some(rel_path) = rel_path.to_str()
        && let Ok(rel_dir) = NPath::<Rel, Dir>::try_from(rel_path)
    {
        backup_entries = browse_handle.list_dir(&rel_dir);
    }

    backup_entries
}

/// Show the backup entries
fn show_backup_entries(backup_entries: &Vec<BackupEntry>, with_path: bool) {
    for backup_entry in backup_entries {
        let mut style = Style::default();

        if backup_entry.flags.contains(Flags::ORPHAN) {
            style = style.cyan();
        }

        if !backup_entry.flags.contains(Flags::VERIFIED) {
            style = style.yellow();
        }

        if backup_entry.flags.contains(Flags::VERIFY_ERROR) {
            style = style.red();
        }

        if backup_entry.rel_path.is_dir() {
            style = style.bold();
        } else if backup_entry.rel_path.is_symlink() {
            style = style.italic();
        }

        let mut entry_string = style.apply_to(backup_entry.name());

        if with_path {
            entry_string = style.apply_to(backup_entry.rel_path.to_nfc().to_string());
        }

        match &backup_entry.rel_path {
            UNPath::File(_file_path) => println!("📄 {}", entry_string),
            UNPath::Dir(_dir_path) => println!("📁 {}", entry_string),
            UNPath::Symlink(_sym_path) => println!("🔗 {}", entry_string),
        }
    }
}
