use crate::cli::actions::Action;

use anyhow::{Context, Result};
use secrecy::SecretString;

pub fn dispatch(matches: &clap::ArgMatches) -> Result<Action> {
    // Closure to return subcommand matches
    let sub_m = |subcommand| -> Result<&clap::ArgMatches> {
        matches
            .subcommand_matches(subcommand)
            .context("arguments not found")
    };

    match matches.subcommand_name() {
        Some("fingerprint") => {
            let sub_m = sub_m("fingerprint")?;
            Ok(Action::Fingerprint {
                key: sub_m.get_one::<String>("key").cloned(),
                user: sub_m.get_one::<String>("user").cloned(),
            })
        }
        Some("create") => {
            let sub_m = sub_m("create")?;
            Ok(Action::Create {
                fingerprint: sub_m.get_one::<String>("fingerprint").cloned(),
                input: sub_m.get_one::<String>("input").cloned(),
                json: sub_m.get_one("json").copied().unwrap_or(false),
                key: sub_m.get_one::<String>("key").cloned(),
                user: sub_m.get_one::<String>("user").cloned(),
                vault: sub_m.get_one::<String>("vault").cloned(),
            })
        }
        Some("view") => {
            let sub_m = sub_m("view")?;
            Ok(Action::View {
                key: sub_m.get_one::<String>("key").cloned(),
                vault: sub_m.get_one::<String>("vault").cloned(),
                output: sub_m.get_one::<String>("output").cloned(),
                passphrase: sub_m
                    .get_one("passphrase")
                    .map(|s: &String| SecretString::from(s.clone())),
            })
        }
        Some("edit") => {
            let sub_m = sub_m("edit")?;
            Ok(Action::Edit {
                key: sub_m.get_one::<String>("key").cloned(),
                passphrase: sub_m
                    .get_one("passphrase")
                    .map(|s: &String| SecretString::from(s.clone())),
                vault: sub_m
                    .get_one::<String>("vault")
                    .cloned()
                    .ok_or_else(|| anyhow::anyhow!("Vault path required"))?,
            })
        }
        _ => Ok(Action::Help),
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;
    use crate::cli::{
        actions::Action,
        commands::{create, edit, fingerprint, view},
    };
    use clap::Command;
    use secrecy::ExposeSecret;

    #[test]
    fn test_dispatch_fingerprint_default() {
        let cmd = Command::new("test").subcommand(fingerprint::subcommand_fingerprint());
        let matches = cmd.try_get_matches_from(vec!["test", "fingerprint"]);
        assert!(matches.is_ok());
        let matches = matches.unwrap();
        let action = dispatch(&matches).unwrap();
        match action {
            Action::Fingerprint { key, user } => {
                assert_eq!(key, None);
                assert_eq!(user, None);
            }
            _ => unreachable!("Wrong action"),
        }
    }

    #[test]
    fn test_dispatch_fingerprint_with_key() {
        let cmd = Command::new("test").subcommand(fingerprint::subcommand_fingerprint());
        let matches =
            cmd.try_get_matches_from(vec!["test", "fingerprint", "--key", "test_data/id_rsa.pub"]);
        assert!(matches.is_ok());
        let matches = matches.unwrap();
        let action = dispatch(&matches).unwrap();
        match action {
            Action::Fingerprint { key, user } => {
                assert_eq!(key, Some("test_data/id_rsa.pub".to_string()));
                assert_eq!(user, None);
            }
            _ => unreachable!("Wrong action"),
        }
    }

    #[test]
    fn test_dispatch_fingerprint_with_user() {
        let cmd = Command::new("test").subcommand(fingerprint::subcommand_fingerprint());
        let matches = cmd.try_get_matches_from(vec!["test", "fingerprint", "--user", "test"]);
        assert!(matches.is_ok());
        let matches = matches.unwrap();
        let action = dispatch(&matches).unwrap();
        match action {
            Action::Fingerprint { key, user } => {
                assert_eq!(key, None);
                assert_eq!(user, Some("test".to_string()));
            }
            _ => unreachable!("Wrong action"),
        }
    }

    #[test]
    fn test_dispatch_fingerprint_with_key_and_user() {
        let cmd = Command::new("test").subcommand(fingerprint::subcommand_fingerprint());
        let matches = cmd.try_get_matches_from(vec![
            "test",
            "fingerprint",
            "--key",
            "test_data/id_rsa.pub",
            "--user",
            "test",
        ]);
        assert!(matches.is_ok());
        let matches = matches.unwrap();
        let action = dispatch(&matches).unwrap();
        match action {
            Action::Fingerprint { key, user } => {
                assert_eq!(key, Some("test_data/id_rsa.pub".to_string()));
                assert_eq!(user, Some("test".to_string()));
            }
            _ => unreachable!("Wrong action"),
        }
    }

    #[test]
    fn test_dispatch_create_default() {
        let cmd = Command::new("test").subcommand(create::subcommand_create());
        let matches = cmd.try_get_matches_from(vec!["test", "create"]);
        assert!(matches.is_ok());
        let matches = matches.unwrap();
        let action = dispatch(&matches).unwrap();
        match action {
            Action::Create {
                fingerprint,
                input,
                json,
                key,
                user,
                vault,
            } => {
                assert_eq!(fingerprint, None);
                assert_eq!(input, None);
                assert!(!json);
                assert_eq!(key, None);
                assert_eq!(user, None);
                assert_eq!(vault, None);
            }
            _ => unreachable!("Wrong action"),
        }
    }

    #[test]
    fn test_dispatch_create_with_json() {
        let cmd = Command::new("test").subcommand(create::subcommand_create());
        let matches = cmd.try_get_matches_from(vec!["test", "create", "--json"]);
        assert!(matches.is_ok());
        let matches = matches.unwrap();
        let action = dispatch(&matches).unwrap();
        match action {
            Action::Create {
                fingerprint,
                input,
                json,
                key,
                user,
                vault,
            } => {
                assert_eq!(fingerprint, None);
                assert_eq!(input, None);
                assert!(json);
                assert_eq!(key, None);
                assert_eq!(user, None);
                assert_eq!(vault, None);
            }
            _ => unreachable!("Wrong action"),
        }
    }

    #[test]
    fn test_dispatch_edit() {
        let cmd = Command::new("test").subcommand(edit::subcommand_edit());
        let matches =
            cmd.try_get_matches_from(vec!["test", "edit", "-p", "secret", "test_data/id_rsa"]);
        assert!(matches.is_ok());
        let matches = matches.unwrap();
        let action = dispatch(&matches).unwrap();
        match action {
            Action::Edit {
                key,
                passphrase,
                vault,
            } => {
                assert_eq!(key, None);
                assert_eq!("secret", passphrase.unwrap().expose_secret());
                assert_eq!(vault, String::from("test_data/id_rsa"));
            }
            _ => unreachable!("Wrong action"),
        }
    }

    #[test]
    fn test_dispatch_edit_no_vault() {
        let cmd = Command::new("test").subcommand(edit::subcommand_edit());
        let matches = cmd.try_get_matches_from(vec!["test", "edit"]);
        assert!(matches.is_err());
    }

    #[test]
    fn test_dispatch_view() {
        let cmd = Command::new("test").subcommand(view::subcommand_view());
        let matches = cmd.try_get_matches_from(vec!["test", "view", "-p", "secret"]);
        assert!(matches.is_ok());
        let matches = matches.unwrap();
        let action = dispatch(&matches).unwrap();
        match action {
            Action::View {
                key,
                vault,
                output,
                passphrase,
            } => {
                assert_eq!(key, None);
                assert_eq!(vault, None);
                assert_eq!(output, None);
                assert_eq!("secret", passphrase.unwrap().expose_secret());
            }
            _ => unreachable!("Wrong action"),
        }
    }

    #[test]
    fn test_dispatch_no_match() {
        let cmd = Command::new("test");
        let matches = cmd.try_get_matches_from(vec!["test"]);
        assert!(matches.is_ok());
        let matches = matches.unwrap();
        let action = dispatch(&matches).unwrap();
        match action {
            Action::Help => {}
            _ => unreachable!("Wrong action"),
        }
    }
}
