mod autocompleter;
mod browse_backup;
mod cli_cmds;
mod console_out;
mod msg_file_logger;
mod progress_bars;

use clap::{CommandFactory, Parser};
use crossbeam_channel::{Sender, unbounded};
use cuba_lib::shared::config_writer::ConfigWriter;
use cuba_lib::{send_error, send_info};
use inquire::Password;
use secrecy::SecretString;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use cuba_lib::core::cuba::{Cuba, RunHandle};
use cuba_lib::shared::config::{
    AccessMode, CONFIG_FILE, config_path, example_config, load_config_from_file,
};
use cuba_lib::shared::message::Message;
use cuba_lib::shared::message::StringError;
use cuba_lib::shared::msg_dispatcher::MsgDispatcher;
use cuba_lib::shared::msg_receiver::MsgReceiver;

use crate::browse_backup::prompt_browse_backup;
use crate::cli_cmds::{Cli, ConfigCommands, ConfigExampleCommands, MainCommands, PasswordCommands};
use crate::console_out::ConsoleOut;
use crate::msg_file_logger::MsgFileLoggerBuilder;
use crate::progress_bars::ProgressBars;

/// A macro the subscribes the `MsgFileLogger` to the `MsgDispatcher`.
macro_rules! use_logger {
    ($msg_logger:ident, $msg_dispatcher:expr) => {{
        let ch_msg_receiver = $msg_dispatcher.subscribe();

        let msg_file_logger = MsgFileLoggerBuilder::new()
            .add_log_file(vec![log::Level::Info], "cuba.info.log")
            // Currently, cuba produces no warnings.
            //.add_log_file(vec![log::Level::Warn], "cuba.warn.log")
            .add_log_file(vec![log::Level::Error], "cuba.error.log")
            .build();

        let msg_receiver = MsgReceiver::new(ch_msg_receiver, Arc::new(msg_file_logger));
        $msg_logger = Some(msg_receiver);

        if let Some(logger) = $msg_logger.as_mut() {
            logger.start();
        }
    }};
}

/// A macro the unsubscribes the `MsgFileLogger` from the `MsgDispatcher`.
macro_rules! unuse_logger {
    ($msg_logger:ident, $msg_dispatcher:expr) => {{
        if let Some(mut logger) = $msg_logger.take() {
            logger.stop();
        }
    }};
}

/// A macro the subscribes the `ConsoleOut` to the `MsgDispatcher`.
macro_rules! use_console_out {
    ($msg_console_out:ident, $msg_dispatcher:expr) => {{
        let ch_msg_receiver = $msg_dispatcher.subscribe();

        let msg_receiver = MsgReceiver::new(ch_msg_receiver, Arc::new(ConsoleOut::new()));
        $msg_console_out = Some(msg_receiver);

        if let Some(console_out) = $msg_console_out.as_mut() {
            console_out.start();
        }
    }};
}

/// A macro the unsubscribes the `ConsoleOut` from the `MsgDispatcher`.
macro_rules! unuse_console_out {
    ($msg_console_out:ident, $msg_dispatcher:expr) => {{
        if let Some(mut console_out) = $msg_console_out.take() {
            console_out.stop();
        }
    }};
}

/// A macro the subscribes the `ProgressBars` to the `MsgDispatcher`.
macro_rules! use_progress {
    ($msg_progress_bars:ident, $msg_dispatcher:expr, $threads:expr) => {{
        let ch_msg_receiver = $msg_dispatcher.subscribe();

        let msg_receiver = MsgReceiver::new(ch_msg_receiver, Arc::new(ProgressBars::new($threads)));

        $msg_progress_bars = Some(msg_receiver);

        if let Some(progress) = $msg_progress_bars.as_mut() {
            progress.start();
        }
    }};
}

/// A macro the unsubscribes the `ProgressBars` from the `MsgDispatcher`.
macro_rules! unuse_progress {
    ($msg_progress_bars:ident, $msg_dispatcher:expr) => {{
        if let Some(mut progress) = $msg_progress_bars.take() {
            progress.stop();
        }
    }};
}

/// A prompt for setting the password.
fn prompt_password(sender: Sender<Arc<dyn Message>>) -> String {
    loop {
        let password_input = Password::new("Enter your password:")
            .without_confirmation()
            .prompt();

        let password = match password_input {
            Ok(password_ok) if !password_ok.is_empty() => password_ok,
            _ => {
                send_error!(
                    sender.clone(),
                    StringError::new("Password cannot be empty. Try again.".to_string())
                );
                continue;
            }
        };

        let confirm_input = Password::new("Confirm your password:")
            .without_confirmation()
            .prompt();

        match confirm_input {
            Ok(confirm) if confirm == password => return password,
            Ok(_) => send_error!(
                sender.clone(),
                StringError::new("Passwords do not match. Try again.".to_string())
            ),
            Err(_) => send_error!(
                sender.clone(),
                StringError::new("Failed to read confirmation. Try again.".to_string())
            ),
        }
    }
}

/// Writes the example config to the CONFIG_FILE.
pub fn write_example_config(sender: Sender<Arc<dyn Message>>) {
    match config_path(AccessMode::Write) {
        Ok(config_path) => {
            let config_file = config_path.join(Path::new(CONFIG_FILE));

            if config_file.exists() {
                print!("{:?}, already exists. Overwrite? [y/N]: ", CONFIG_FILE);
                if let Err(error) = io::stdout().flush() {
                    send_error!(sender.clone(), error);
                    return;
                }

                let mut input = String::new();
                if let Err(error) = io::stdin().read_line(&mut input) {
                    send_error!(sender.clone(), error);
                    return;
                }

                let trimmed = input.trim().to_lowercase();
                if trimmed != "y" && trimmed != "yes" {
                    send_error!(
                        sender.clone(),
                        StringError::new("Aborted. Existing file was not overwritten.".to_string())
                    );
                    return;
                }
            }

            // Write example config to file.
            ConfigWriter::example(sender, &config_file);
        }
        Err(error) => {
            send_error!(sender.clone(), error);
        }
    }
}

/// Loads the config before needed.
fn load_config(cuba: &mut Cuba, config_file: &Option<PathBuf>, sender: Sender<Arc<dyn Message>>) {
    if let Some(config_file) = config_file {
        // Load config.
        if let Some(config) = load_config_from_file(sender.clone(), config_file) {
            cuba.set_config(config);
        }

        return;
    }

    match config_path(AccessMode::Read) {
        Ok(config_path) => {
            let config_file = config_path.join(Path::new(CONFIG_FILE));

            // Load config.
            if let Some(config) = load_config_from_file(sender.clone(), &config_file) {
                cuba.set_config(config);
            }
        }
        Err(error) => {
            send_error!(sender, error);
        }
    }
}

fn main() {
    let (sender, receiver) = unbounded::<Arc<dyn Message>>();

    let mut msg_dispatcher = MsgDispatcher::new(receiver.clone());

    msg_dispatcher.start();

    #[allow(unused_assignments)]
    let mut msg_console_out: Option<MsgReceiver> = None;
    #[allow(unused_assignments)]
    let mut msg_logger: Option<MsgReceiver> = None;
    #[allow(unused_assignments)]
    let mut msg_progress_bars: Option<MsgReceiver> = None;

    use_logger!(msg_logger, msg_dispatcher);
    use_console_out!(msg_console_out, msg_dispatcher);

    // Show help if no arguments are passed.
    if std::env::args().len() == 1 {
        Cli::command().print_help().unwrap();
    } else {
        let mut cuba = Cuba::new(sender.clone());

        match Cli::try_parse() {
            Ok(cli) => match &cli.command {
                MainCommands::Backup { backup, config } => {
                    load_config(&mut cuba, config, sender.clone());

                    if let Some(config) = cuba.requires_config() {
                        send_info!(sender, "Start backup of {:?}", backup);
                        unuse_console_out!(msg_console_out, msg_dispatcher);
                        use_progress!(msg_progress_bars, msg_dispatcher, config.transfer_threads);

                        cuba.run_backup(RunHandle::default(), backup);

                        unuse_progress!(msg_progress_bars, msg_dispatcher);
                        use_console_out!(msg_console_out, msg_dispatcher);
                        send_info!(sender, "Backup finished");
                    }
                }
                MainCommands::Restore { restore, config } => {
                    load_config(&mut cuba, config, sender.clone());

                    if let Some(config) = cuba.requires_config() {
                        send_info!(sender, "Start restore of {:?}", restore);
                        unuse_console_out!(msg_console_out, msg_dispatcher);
                        use_progress!(msg_progress_bars, msg_dispatcher, config.transfer_threads);

                        cuba.run_restore(RunHandle::default(), restore);

                        unuse_progress!(msg_progress_bars, msg_dispatcher);
                        use_console_out!(msg_console_out, msg_dispatcher);
                        send_info!(sender, "Restore finished");
                    }
                }
                MainCommands::Verify {
                    backup,
                    all,
                    config,
                } => {
                    load_config(&mut cuba, config, sender.clone());

                    if let Some(config) = cuba.requires_config() {
                        send_info!(sender, "Start verify of {:?}", backup);
                        unuse_console_out!(msg_console_out, msg_dispatcher);
                        use_progress!(msg_progress_bars, msg_dispatcher, config.transfer_threads);

                        cuba.run_verify(RunHandle::default(), backup, *all);

                        unuse_progress!(msg_progress_bars, msg_dispatcher);
                        use_console_out!(msg_console_out, msg_dispatcher);
                        send_info!(sender, "Verify finished");
                    }
                }
                MainCommands::Clean { backup, config } => {
                    load_config(&mut cuba, config, sender.clone());

                    if let Some(config) = cuba.requires_config() {
                        send_info!(sender, "Start clean of {:?}", backup);
                        unuse_console_out!(msg_console_out, msg_dispatcher);
                        use_progress!(msg_progress_bars, msg_dispatcher, config.transfer_threads);

                        cuba.run_clean(RunHandle::default(), backup);

                        unuse_progress!(msg_progress_bars, msg_dispatcher);
                        use_console_out!(msg_console_out, msg_dispatcher);
                        send_info!(sender, "Clean finished");
                    }
                }
                MainCommands::Password { command } => match command {
                    PasswordCommands::Set { id, stdin } => {
                        let mut password: Option<String> = None;

                        if *stdin {
                            let mut input = String::new();

                            match std::io::stdin().read_line(&mut input) {
                                Ok(_input_size) => {
                                    password = Some(input.trim().to_string());
                                }
                                Err(error) => {
                                    send_error!(sender, error);
                                }
                            }
                        } else {
                            password = Some(prompt_password(sender));
                        }

                        if let Some(password) = password {
                            cuba.set_password(id, &SecretString::from(password));
                        }
                    }
                    PasswordCommands::Delete { id } => {
                        cuba.delete_password(id);
                    }
                    PasswordCommands::List => {
                        if let Some(password_ids) = cuba.get_password_ids() {
                            println!("{:?}", password_ids);
                        }
                    }
                },
                MainCommands::Config { command } => match command {
                    ConfigCommands::Example { command } => match command {
                        ConfigExampleCommands::Show => {
                            println!("{}", example_config());
                        }
                        ConfigExampleCommands::Write => {
                            write_example_config(sender);
                        }
                    },
                },
                MainCommands::Browse { backup, config } => {
                    load_config(&mut cuba, config, sender.clone());

                    if let Some(browse_handle) = cuba.browse_backup(backup) {
                        prompt_browse_backup(backup, &browse_handle);
                    }
                }
            },
            Err(err) => {
                send_error!(sender.clone(), StringError::new(format!("{}", err)));
            }
        }
    }

    unuse_logger!(msg_logger, msg_dispatcher);
    unuse_console_out!(msg_console_out, msg_dispatcher);
    msg_dispatcher.stop();
}
