//! `config validate` subcommand

use crate::{
    command::config::{current_revision, RevisionCheckError},
    config::{client, server, CLIENT_CONFIG_PATH, SERVER_CONFIG_PATH},
};
use color_print::cprintln;
use enum_iterator::Sequence;
use std::{fs, io::Read};
use validator::Validate;

/// Errors that can happen in `config validate` subcommand
#[derive(thiserror::Error, Debug)]
pub(crate) enum ValidationError {
    #[error(transparent)]
    ReadFile(#[from] std::io::Error),
    #[error(transparent)]
    UnknownRevision(#[from] RevisionCheckError),
    #[error("config files are not the latest revision")]
    OutOfDate,
    #[error("invalid config file")]
    InvalidConfig,
}

#[derive(thiserror::Error, Debug)]
enum ReadError {
    #[error(transparent)]
    ReadFile(#[from] std::io::Error),
    #[error("the config file is not written in the correct format")]
    InvalidFormat(#[from] toml::de::Error),
}

pub(super) fn run() -> Result<(), ValidationError> {
    if current_revision()?.next().is_some() {
        cprintln!("Please first run `<bold>fishctl config update</>` to update your config files.");
        return Err(ValidationError::OutOfDate);
    }

    let server_validation_result = match read_server_toml() {
        Ok(config) => config.validate().map_err(|err| {
            cprintln!("<r!><bold>config/server.toml is invalid.</></>\n{}", err);
            ValidationError::InvalidConfig
        }),
        Err(ReadError::InvalidFormat(err)) => {
            cprintln!("<r!><bold>config/server.toml is invalid.</></>\n{}", err);
            Err(ValidationError::InvalidConfig)
        }
        Err(ReadError::ReadFile(err)) => Err(ValidationError::ReadFile(err)),
    };

    let client_validation_result = match read_client_toml() {
        Ok(config) => config.validate().map_err(|err| {
            cprintln!(
                "<r!><bold>{} is invalid.</></>\n{}",
                CLIENT_CONFIG_PATH,
                err
            );
            ValidationError::InvalidConfig
        }),
        Err(ReadError::InvalidFormat(err)) => {
            cprintln!(
                "<r!><bold>{} is invalid.</></>\n{}",
                CLIENT_CONFIG_PATH,
                err
            );
            Err(ValidationError::InvalidConfig)
        }
        Err(ReadError::ReadFile(err)) => Err(ValidationError::ReadFile(err)),
    };

    if server_validation_result.is_ok() && client_validation_result.is_ok() {
        cprintln!(
            "<g!><bold>{} and {} are valid!</></>",
            SERVER_CONFIG_PATH,
            CLIENT_CONFIG_PATH
        );
        cprintln!("<bold>Note:</> This command only checks the format of the config files, and its result does not guarantee the correctness of the value.");
    }

    server_validation_result.and(client_validation_result)
}

fn read_server_toml() -> Result<server::Config, ReadError> {
    let mut file = fs::File::open(SERVER_CONFIG_PATH)?;

    let mut buffer = String::new();
    file.read_to_string(&mut buffer)?;

    toml::from_str(&buffer).map_err(ReadError::InvalidFormat)
}

fn read_client_toml() -> Result<client::Config, ReadError> {
    let mut file = fs::File::open(CLIENT_CONFIG_PATH)?;

    let mut buffer = String::new();
    file.read_to_string(&mut buffer)?;

    toml::from_str(&buffer).map_err(ReadError::InvalidFormat)
}