use crate::error::Error::{self, *};
use async_trait::async_trait;
use clap::{Arg, ArgAction, ArgMatches, Command};
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use std::{env, fs::File};

use crate::{
	config::rezon_config::RezonConfig,
	util::{get_and_display_model_names, BASE_BFF_URL},
};

use super::RezonCommand;

pub struct Report {}

#[derive(Serialize, Deserialize, Debug)]
pub struct ReportBody {
	pub days: Option<i32>,
	pub instances: Option<i32>,
}

#[async_trait]
impl RezonCommand for Report {
	fn cmd() -> clap::Command {
		Command::new("report")
			.about("Generate a report of all the specified model's explanations")
			.arg(
				Arg::new("days")
					.default_value("30")
					.short('d')
					.long("days")
					.value_name("DAYS")
					.value_parser(clap::value_parser!(i32))
					.help("Specify the number of days to generate the report for."),
			)
			.arg(
				Arg::new("instances")
					.conflicts_with("days")
					.short('i')
					.long("instances")
					.value_name("INSTANCES")
					.value_parser(clap::value_parser!(i32))
					.help("Specify the number of instances to generate the report for."),
			)
			.arg(
				Arg::new("model_name")
					.short('m')
					.long("model")
					.action(ArgAction::Set)
					.value_name("MODEL_NAME")
					.required(false)
					.help("Name of the model to invoke"),
			)
	}

	async fn execute(args: &ArgMatches) -> Result<(), Error> {
		generate_report(RezonConfig::read_config()?, args).await?;
		Ok(())
	}
}

async fn generate_report(config: RezonConfig, args: &ArgMatches) -> Result<(), Error> {
	//This unwrap is safe because we check for the config earlier
	let days = args.get_one::<i32>("days").cloned();
	let instances = args.get_one::<i32>("instances").cloned();
	let model_name: String = match args.get_one::<String>("model_name") {
		Some(n) => n.to_string(),
		None => {
			// No model name provided, fetch list of available models and return
			get_and_display_model_names(&config).await?
		}
	};

	let client = Client::new();
	let response = client
		.get(format!("{}/explanations/getByModelName/{}", BASE_BFF_URL, model_name.clone()))
		.header("Authorization", config.config.api_key.clone())
		.header("Content-Type", "application/json")
		.json(&ReportBody { days, instances })
		.send()
		.await?;

	if response.status() != StatusCode::OK {
		return Err(NotOk {
			response: response.text().await?,
		});
	}

	let response_text = response.text().await?;

	// Parse the text into a Value
	let v: serde_json::Value = serde_json::from_str(&response_text)?;

	let current_dir = match env::current_dir() {
		Ok(dir) => dir,
		Err(e) => {
			return Err(Io {
				path: std::path::PathBuf::from("current directory"),
				source: e,
			})
		}
	};
	let mut file_path = current_dir;
	file_path.push(format!("{}.json", chrono::Utc::now().format("%Y%m%d_%H%M%S").to_string())); // Use date range or curr time and instance count for file title
	let file = match File::create(&file_path) {
		Ok(file) => file,
		Err(e) => return Err(Io { path: file_path, source: e }),
	};

	// Write the Value into the file with pretty formatting
	serde_json::to_writer_pretty(file, &v)?;

	println!("Report saved at {}", file_path.display());

	Ok(())
}
