#![deny(clippy::all, clippy::pedantic, unsafe_code)]

pub(crate) mod config;
pub(crate) mod convert;
pub(crate) mod env;
pub(crate) mod facts;
pub(crate) mod installer;
pub(crate) mod jobs;
pub(crate) mod preflight;
pub(crate) mod runner;
pub(crate) mod runnerstate;
pub(crate) mod status;
pub(crate) mod tags;
pub(crate) mod template;

use std::io;

use camino::Utf8PathBuf;
use clap::Parser;
use thiserror::Error as ThisError;

use crate::facts::Facts;

#[derive(Debug, Parser)]
#[command(version)]
struct Cli {
    #[arg(default_values_t = config::default_config_paths(), long)]
    /// paths to possible config file(s),
    /// will use the first one that exists
    config: Vec<Utf8PathBuf>,
    #[arg(long)]
    skip_tags: Vec<String>,
    #[arg(default_values_t = facts::default_tags(), long)]
    tags: Vec<String>,
}

#[derive(Debug, ThisError)]
enum Error {
    #[error(transparent)]
    Config {
        #[from]
        source: config::Error,
    },
    #[error(transparent)]
    Facts {
        #[from]
        source: facts::Error,
    },
    #[error(transparent)]
    Io {
        #[from]
        source: io::Error,
    },
    #[error(transparent)]
    Job {
        #[from]
        source: jobs::Error,
    },
    #[error(transparent)]
    Preflight {
        #[from]
        source: preflight::Error,
    },
}

type Result<T> = std::result::Result<T, Error>;

#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
    let args = Cli::parse();

    let mut facts = Facts::gather();
    facts.skip_tags = args.skip_tags.into_iter().collect();
    facts.tags = args.tags.into_iter().collect();
    facts.validate()?;

    let mut config_path: Option<Utf8PathBuf> = None;
    for c in args.config {
        eprintln!("finding: {}", &c);
        let c = if c.is_absolute() {
            c
        } else {
            crate::env::current_dir().join(c).canonicalize_utf8()?
        };
        if c.exists() {
            config_path = Some(c);
            break;
        }
    }

    let config_path =
        config_path.expect("`--config <CONFIG>` option should refer to file that exists");

    eprintln!("reading: {}", &config_path);
    facts.main_file = config_path.clone();

    let rm = config::read_main_toml(config_path)?;
    rm.validate()?;

    runner::run(rm.jobs, &facts).await;

    Ok(())
}
