mod db;
mod api;
mod config;
mod up;

use config::Config;
use db::Database;

use clap::Parser;


#[derive(Parser)]
struct Cli {
	/// path to storage file, if not given will operate in memory
	storage: Option<String>,

	/// path to config file
	#[arg(short, long, default_value = "upp.toml")]
	config: String,

	/// host to bind api onto
	#[arg(short, long, default_value = "127.0.0.1:7717")]
	addr: String,
}

fn main() {
	let cli = Cli::parse();

	let raw_config = match std::fs::read_to_string(&cli.config) {
		Ok(x) => x,
		Err(e) => {
			println!("could not read config: {e}");
			return;
		},
	};

	let config = match toml::from_str::<Config>(&raw_config) {
		Ok(x) => x,
		Err(e) => {
			println!("invalid config file: {e}");
			return;
		},
	};

	let db =  match Database::open(cli.storage.as_deref()) {
		Ok(x) => x,
		Err(e) => {
			println!("could not connect do database: {e}");
			return;
		},
	};

	let mut runtime_builder = {
		#[cfg(feature = "multi-thread")]
		{
			tokio::runtime::Builder::new_multi_thread()
		}

		#[cfg(not(feature = "multi-thread"))]
		{
			tokio::runtime::Builder::new_current_thread()
		}
	};

	if let Err(e) = runtime_builder
		.enable_all()
		.build()
		.expect("could not create tokio runtime")
		.block_on(async move {
			up::work(config.clone(), db.clone()).await?; // <<-- this spawns background workers

			api::serve(config, db, &cli.addr).await?; // <<-- this blocks!

			// TODO it's a bit weird that these two work so differently, can we make them more similar?

			Ok::<(), Box<dyn std::error::Error>>(()) // ughhh
		})
	{
		println!("event loop terminated with error: {e}");
		eprintln!("{e:?}");
	}
}