use std::{env, fs, path::PathBuf};

use gotcha_gitea::Client;
use security_framework::passwords::{get_generic_password, set_generic_password};

use crate::domain::{Preferences, Server, open_status};

pub fn favorite_key(server: &str, owner: &str, repository: &str) -> String {
    format!("{server}|{owner}/{repository}")
}

pub fn validate_server(name: &str, url: &str, token: &str) -> Result<Server, String> {
    let name = name.trim();
    let url = url.trim().trim_end_matches('/');
    let token = token.trim();
    if name.is_empty() {
        return Err("Give this server a name.".into());
    }
    if token.is_empty() {
        return Err("Enter an access token.".into());
    }
    Client::new(url, Some(token)).map_err(|error| error.to_string())?;
    Ok(Server {
        name: name.into(),
        url: url.into(),
        token: token.into(),
    })
}

pub fn load_preferences() -> Result<Preferences, String> {
    let path = preferences_path()?;
    if !path.exists() {
        return Ok(Preferences::default());
    }
    let mut preferences: Preferences =
        serde_json::from_slice(&fs::read(&path).map_err(|error| error.to_string())?)
            .map_err(|error| format!("Cannot read {}: {error}", path.display()))?;
    if !matches!(preferences.issue_status.as_str(), "open" | "closed") {
        preferences.issue_status = open_status();
    }
    if !matches!(preferences.pull_status.as_str(), "open" | "closed") {
        preferences.pull_status = open_status();
    }
    for server in &mut preferences.servers {
        server.token = String::from_utf8(
            get_generic_password("de.rfc1437.gotcha", &keychain_account(server))
                .map_err(|error| format!("Cannot read the token for {}: {error}", server.name))?,
        )
        .map_err(|_| format!("The token for {} is not valid text.", server.name))?;
    }
    Ok(preferences)
}

pub fn save_preferences(preferences: &Preferences) -> Result<(), String> {
    let path = preferences_path()?;
    let parent = path.parent().ok_or("Invalid app data directory.")?;
    fs::create_dir_all(parent).map_err(|error| error.to_string())?;
    let temporary = path.with_extension("tmp");
    fs::write(
        &temporary,
        serde_json::to_vec(preferences).map_err(|error| error.to_string())?,
    )
    .map_err(|error| error.to_string())?;
    fs::rename(&temporary, &path).map_err(|error| error.to_string())
}

pub fn save_server_token(server: &Server) -> Result<(), String> {
    set_generic_password(
        "de.rfc1437.gotcha",
        &keychain_account(server),
        server.token.as_bytes(),
    )
    .map_err(|error| format!("Cannot save the token for {}: {error}", server.name))
}

fn preferences_path() -> Result<PathBuf, String> {
    let home = env::var_os("HOME").ok_or("Cannot find the app data directory.")?;
    Ok(PathBuf::from(home).join("Library/Application Support/Gotcha/preferences.json"))
}

fn keychain_account(server: &Server) -> String {
    format!("{}|{}", server.name, server.url)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn validates_servers_and_builds_stable_favorite_keys() {
        assert!(validate_server("Work", "https://gitea.example.com/", "secret").is_ok());
        assert!(validate_server("", "https://gitea.example.com", "secret").is_err());
        assert!(validate_server("Work", "file:///tmp/gitea", "secret").is_err());
        assert_eq!(
            favorite_key("https://gitea.example.com", "octo", "demo"),
            "https://gitea.example.com|octo/demo"
        );
        assert_eq!(Preferences::default().pull_status, "open");
        assert_eq!(
            Preferences::default().appearance,
            crate::domain::AppearanceMode::Auto
        );
        assert_eq!(
            serde_json::from_str::<Preferences>(r#"{"appearance":"dark"}"#)
                .unwrap()
                .appearance,
            crate::domain::AppearanceMode::Dark
        );
        assert_eq!(
            serde_json::from_str::<Preferences>(r#"{"appearance":"future-mode"}"#)
                .unwrap()
                .appearance,
            crate::domain::AppearanceMode::Auto
        );
        assert_eq!(
            crate::domain::AppearanceMode::from_index(1),
            Some(crate::domain::AppearanceMode::Light)
        );
        assert_eq!(crate::domain::AppearanceMode::from_index(3), None);
    }
}
