extern crate core;

use crate::cmd::CliContext;
use clap::Parser;
use eternaltwin_core::types::WeakError;
use std::collections::BTreeMap;
use std::error::Error;
use std::path::PathBuf;
use std::process::exit;

pub mod cmd;
pub mod event;
pub mod output;

#[derive(Debug, Parser)]
#[clap(author = "Eternaltwin", name = "eternaltwin", version)]
pub struct CliArgs {
  #[clap(subcommand)]
  command: CliCommand,
}

#[derive(Debug, Parser)]
pub enum CliCommand {
  /// Run the Eternaltwin backend
  #[clap(name = "backend")]
  Backend(cmd::backend::Args),
  /// Test the config
  #[clap(name = "config")]
  Config(cmd::config::Args),
  /// Run the one-off Twinoid archival
  #[clap(name = "tidsave")]
  TidSave(cmd::tidsave::Args),
  /// Manage the db schema and state
  #[clap(name = "db")]
  Db(cmd::db::Args),
  /// Dump the DB state into a directory
  #[clap(name = "dump")]
  Dump(cmd::dump::Args),
  /// Control background jobs from the command line
  #[clap(name = "job")]
  Job(cmd::job::Args),
  /// Run the full Eternaltwin server
  #[cfg(feature = "start")]
  #[clap(name = "start")]
  Start(cmd::start::Args),
  /// Print version details
  #[clap(name = "version")]
  Version(cmd::version::Args),
}

pub async fn run(args: &CliArgs) -> Result<(), WeakError> {
  let env: BTreeMap<String, String> = std::env::vars().collect();
  let env = &env;
  let working_dir: PathBuf = std::env::current_dir().expect("failed to retrieve current working directory");
  let working_dir = working_dir.as_path();
  let out = std::io::stdout();
  match &args.command {
    CliCommand::Backend(ref args) => {
      cmd::backend::run(CliContext {
        args,
        env,
        working_dir,
        out,
      })
      .await
    }
    CliCommand::Config(ref args) => {
      cmd::config::run(CliContext {
        args,
        env,
        working_dir,
        out,
      })
      .await
    }
    CliCommand::TidSave(ref args) => {
      cmd::tidsave::run(CliContext {
        args,
        env,
        working_dir,
        out,
      })
      .await
    }
    CliCommand::Db(ref args) => {
      cmd::db::run(CliContext {
        args,
        env,
        working_dir,
        out,
      })
      .await
    }
    CliCommand::Dump(ref args) => {
      cmd::dump::run(CliContext {
        args,
        env,
        working_dir,
        out,
      })
      .await
    }
    CliCommand::Job(ref args) => {
      cmd::job::run(CliContext {
        args,
        env,
        working_dir,
        out,
      })
      .await
    }
    #[cfg(feature = "start")]
    CliCommand::Start(ref args) => {
      cmd::start::run(CliContext {
        args,
        env,
        working_dir,
        out,
      })
      .await
    }
    CliCommand::Version(ref args) => {
      cmd::version::run(CliContext {
        args,
        env,
        working_dir,
        out,
      })
      .await
    }
  }
}

#[tokio::main]
pub async fn main() {
  let args: CliArgs = CliArgs::parse();

  let res = run(&args).await;

  if let Err(e) = res {
    eprintln!("ERROR: {}", &e);
    let mut source: Option<&dyn Error> = e.source();
    for _ in 0..100 {
      match source {
        Some(src) => {
          eprintln!("-> {}", src);
          source = src.source();
        }
        None => break,
      }
    }
    exit(1)
  }
}
