//! `config update` subcommand

mod v1;

use crate::{
    command::config::{current_revision, RevisionCheckError},
    config::{Revision, CLIENT_CONFIG_PATH, SERVER_CONFIG_PATH},
};
use chrono::Local;
use color_print::cprintln;
use enum_iterator::Sequence;
use std::{fs, io};

/// Errors that happen in `config update` subcommand
#[derive(thiserror::Error, Debug)]
pub(crate) enum UpdateError {
    #[error(transparent)]
    RevisionCheck(#[from] RevisionCheckError),
    #[error(transparent)]
    FileOperation(#[from] io::Error),
    #[error("downgrading is not supported")]
    Downgrade,
    #[error("failed to update the config to revision 1")]
    V1(#[from] v1::Error),
}

pub(super) async fn run(revision: Option<Revision>) -> Result<(), UpdateError> {
    let current = current_revision()?;

    if current.next().is_none() {
        println!("Your config files are already up-to-date! (as of this fishctl release)");
        return Ok(());
    }
    if current != Revision::V0 {
        take_backup()?;
    }

    match revision {
        Some(target) => update(current, target).await,
        None => update(current, Revision::last().unwrap()).await,
    }
}

fn take_backup() -> Result<(), UpdateError> {
    let current_time = Local::now().format("%Y%m%d%H%M%S").to_string();
    let server_backup_filename = format!("{}-backup-{}", SERVER_CONFIG_PATH, current_time);
    let client_backup_filename = format!("{}-backup-{}", CLIENT_CONFIG_PATH, current_time);

    cprintln!(
        "Saving server config backup as <bold>{}</>...",
        server_backup_filename
    );
    fs::copy(SERVER_CONFIG_PATH, server_backup_filename)?;

    cprintln!(
        "Saving client config backup as <bold>{}</>...",
        client_backup_filename
    );
    fs::copy(CLIENT_CONFIG_PATH, client_backup_filename)?;

    Ok(())
}

/// Updates config files to the specified revision.
async fn update(mut current: Revision, target: Revision) -> Result<(), UpdateError> {
    if current > target {
        return Err(UpdateError::Downgrade);
    }
    while current < target {
        let next = current.next().unwrap();
        update_one_revision(next.clone()).await?;
        current = next;
    }
    Ok(())
}

/// Updates config file revision from `target - 1` to `target`.
async fn update_one_revision(target: Revision) -> Result<(), UpdateError> {
    println!(
        "Updating the config to revision {}...",
        target.clone() as u8
    );
    match target {
        Revision::V0 => unreachable!(),
        Revision::V1 => v1::run().await?,
    }
    Ok(())
}