use crate::{
	commands::{self, RezonCommand},
	config::rezon_config::RezonConfig,
};
use clap::{command, crate_version, ArgMatches, Command};
use std::{future::Future, pin::Pin};

pub type App = Command;

// TODO: this is here for when we need to have global state stored inside of the CLI (we may not need this)
#[derive(Clone)]
pub struct RezonCLI {
	// This config can be used for global env vars that can be passed on CLI init
	pub config: Option<RezonConfig>,
}

impl RezonCLI {
	pub fn new(config: Option<RezonConfig>) -> Self {
		Self { config }
	}

	pub fn parse() -> App {
		let usage = "rapid [SUBCOMMAND] [OPTIONS]";
		command!()
			.allow_external_subcommands(true)
			.disable_colored_help(false)
			.override_usage(usage)
			.long_version(crate_version!())
			// .help_template(get_help_template())
			.subcommands(Self::commands())
	}

	pub fn commands() -> Vec<Command> {
		vec![
			commands::init::Init::cmd(),
			commands::deploy::Deploy::cmd(),
			commands::run::Run::cmd(),
			commands::report::Report::cmd(),
		]
	}

	pub fn execute_command(
		cmd: &str,
	) -> Option<fn(Option<RezonConfig>, ArgMatches) -> Pin<Box<dyn Future<Output = Result<(), CliError<'static>>> + Send>>> {
		let command_resolver = match cmd {
			"init" => commands::init::Init::execute,
			"deploy" => commands::deploy::Deploy::execute,
			"run" => commands::run::Run::execute,
			"report" => commands::report::Report::execute,
			_ => return None,
		};

		Some(command_resolver)
	}

	pub async fn run(self, args: ArgMatches) -> Result<(), CliError<'static>> {
		if let Some((cmd, args)) = args.subcommand() {
			// Since we did find a sub-command match, let's execute the command
			if cmd == "init" {
				// Handle 'init' command separately
				if let Some(cm) = Self::execute_command(cmd) {
					cm(None, args.to_owned()).await?;
				} else {
					println!("{}", get_help_template());
					return Err(CliError::new("Invalid command", 64));
				}
			} else {
				// For all other commands, config must be available
				if let Some(config) = &self.config {
					if let Some(cm) = Self::execute_command(cmd) {
						cm(Some(config.clone()), args.to_owned()).await?;
					} else {
						println!("{}", get_help_template());
						return Err(CliError::new("Invalid command", 64));
					}
				} else {
					return Err(CliError::new(
						"Could not find rezon.toml to load configuration. Run `rezon init` to create one.",
						1,
					));
				}
			}
		} else {
			// If no sub-command was matched, show the help template and return an error
			println!("{}", get_help_template());
			return Err(CliError::new("Invalid command", 64));
		}

		// This outputs only when a command succeeds (would be cool to capture analytics here at some point)
		Ok(())
	}
}

fn get_help_template() -> String {
	String::from("Hello world from the help template!")
}

#[derive(Debug)]
pub struct CliError<'a> {
	pub error: Option<&'a str>,
	pub exit_code: i32,
}

impl<'a> CliError<'a> {
	pub fn new(error: &'a str, code: i32) -> CliError<'a> {
		CliError {
			error: Some(error),
			exit_code: code,
		}
	}
}
