use app::App;
use log::trace;
use reqwest::{StatusCode, Url};
use serde::{de::DeserializeOwned, Deserialize};
pub mod app;
pub mod system;
pub struct Protontweaks {
    url: String,
}
#[derive(Debug, Deserialize)]
pub struct AppsList {
    pub sha: String,
    pub short_sha: String,
    pub apps: Vec<MicroApp>,
}
#[derive(Debug, Deserialize)]
pub struct MicroApp {
    pub id: String,
    pub name: String,
}
impl Default for Protontweaks {
    fn default() -> Self {
        Self {
            url: "https://api.protontweaks.com/v4".to_string(),
        }
    }
}
impl Protontweaks {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn new_with_url(url: &str) -> Self {
        Self {
            url: url.to_string(),
            ..Self::default()
        }
    }
    pub fn to_url(&self, endpoint: &str) -> Url {
        let url = Url::parse(&self.url).unwrap();
        return url.join(endpoint).unwrap();
    }
    async fn get<T: DeserializeOwned>(&self, url: &str) -> Result<T, String> {
        let url = self.to_url(url);
        trace!("Requesting apps from '{url}'...");
        let response = reqwest::get(url.clone())
            .await
            .map_err(|error| error.to_string())?;
        if response.status().is_success() {
            response
                .json::<T>()
                .await
                .map_err(|_| "Failed to parse apps.json".to_string())
        } else {
            match response.status() {
                StatusCode::NOT_FOUND => {
                    Err(format!("Unable to locate file at '{}'.", url.to_string()))
                }
                _ => Err(response.error_for_status().unwrap_err().to_string()),
            }
        }
    }
    pub async fn try_apps_list(&self) -> Result<AppsList, String> {
        self.get::<AppsList>("apps.json").await
    }
    pub async fn apps_list(&self) -> AppsList {
        self.try_apps_list().await.unwrap()
    }
    pub async fn try_apps(&self) -> Result<Vec<MicroApp>, String> {
        self.try_apps_list().await.map(|apps_list| apps_list.apps)
    }
    pub async fn apps(&self) -> Vec<MicroApp> {
        self.try_apps().await.unwrap()
    }
    pub async fn try_app_ids(&self) -> Result<Vec<String>, String> {
        self.try_apps()
            .await
            .map(|apps| apps.iter().map(|app| app.id.clone()).collect())
    }
    pub async fn app_ids(&self) -> Vec<String> {
        self.try_app_ids().await.unwrap()
    }
    pub async fn try_app(&self, app_id: &str) -> Result<App, String> {
        self.get::<App>(&format!("{app_id}.json")).await
    }
    pub async fn app(&self, app_id: &str) -> App {
        self.try_app(app_id).await.unwrap()
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[tokio::test]
    async fn apps_list() {
        let api = Protontweaks::new();
        assert!(
            api.try_apps_list().await.unwrap().apps.len() > 0,
            "Should contain a list of apps"
        );
        assert!(
            api.apps_list().await.apps.len() > 0,
            "Should contain a list of apps"
        );
    }
    #[tokio::test]
    async fn apps() {
        let api = Protontweaks::new();
        assert!(
            api.try_apps().await.unwrap().len() > 0,
            "Should be a list of apps"
        );
        assert!(api.apps().await.len() > 0, "Should be a list of apps");
    }
    #[tokio::test]
    async fn app_ids() {
        let api = Protontweaks::new();
        assert!(
            api.try_app_ids().await.unwrap().len() > 0,
            "Should be a list of app ids"
        );
        assert!(api.app_ids().await.len() > 0, "Should be a list of app ids");
    }
    #[tokio::test]
    async fn try_app() {
        let expected_id = "644930";
        let api = Protontweaks::new();
        let app = api.try_app(expected_id).await.unwrap();
        assert_eq!(app.id, expected_id);
        assert_eq!(app.issues.len(), 1);
        assert_eq!(app.tweaks.tricks.len(), 1);
        assert_eq!(app.tweaks.env.len(), 0);
        assert_eq!(app.tweaks.settings.gamemode, Some(true));
        assert_eq!(app.tweaks.settings.mangohud, Some(true));
    }
    #[tokio::test]
    async fn app() {
        let expected_id = "644930";
        let api = Protontweaks::new();
        let app = api.app(expected_id).await;
        assert_eq!(app.id, expected_id);
        assert_eq!(app.issues.len(), 1);
        assert_eq!(app.tweaks.tricks.len(), 1);
        assert_eq!(app.tweaks.env.len(), 0);
        assert_eq!(app.tweaks.settings.gamemode, Some(true));
        assert_eq!(app.tweaks.settings.mangohud, Some(true));
    }
}