#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
#![warn(clippy::cargo)]

mod common;

use anyhow::Error;
use common::identity::{Identity, SubCommand, default, prompt};
use common::{Interaction, InteractionType, SecondAttempt};

fn default_id_name() -> Interaction {
    Interaction::new(
        prompt::ID_NAME,
        InteractionType::Input(default::ID_NAME.to_string()),
    )
}

#[test]
fn new_minimal() -> Result<(), Error> {
    let identity = Identity::new(
        "new_minimal",
        default_id_name(),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )?;

    identity.run(&SubCommand::New, Vec::new())?;
    Ok(())
}

#[test]
fn new_full() -> Result<(), Error> {
    let identity = Identity::new(
        "new_full",
        default_id_name(),
        Some(default::FULL_NAME.to_string()),
        Some(Interaction::new(
            prompt::EMAIL,
            InteractionType::Input(default::EMAIL.to_string()),
        )),
        Some(Interaction::new(
            prompt::EXPIRY_DATE,
            InteractionType::Input(default::EXPIRY.to_string()),
        )),
        Some(Interaction::new(
            prompt::LOGIN,
            InteractionType::Input(default::LOGIN.to_string()),
        )),
        Some(Interaction::new(
            prompt::ORIGIN,
            InteractionType::Input(default::ORIGIN.to_string()),
        )),
        Some(Interaction::new(
            prompt::PASSWORD,
            InteractionType::Password {
                input: default::PASSWORD.to_string(),
                confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
            },
        )),
        Some(Interaction::new(
            prompt::SELECT_KEY,
            InteractionType::Input(default::SSH.to_string()),
        )),
    )?;

    identity.run(&SubCommand::New, Vec::new())?;
    Ok(())
}

#[test]
fn new_email() -> Result<(), Error> {
    let identity = Identity::new(
        "new_email",
        default_id_name(),
        None,
        Some(
            Interaction::new(
                prompt::EMAIL,
                InteractionType::Input(String::from("BAD-EMAIL")),
            )
            .with_second_attempt(SecondAttempt::new(
                InteractionType::Input(default::EMAIL.to_string()),
                "Invalid email address",
            )?)?,
        ),
        None,
        None,
        None,
        None,
        None,
    )?;

    identity.run(&SubCommand::New, Vec::new())?;
    Ok(())
}

#[test]
fn new_expiry() -> Result<(), Error> {
    let identity = Identity::new(
        "new_expiry",
        default_id_name(),
        None,
        None,
        Some(
            Interaction::new(
                prompt::EXPIRY_DATE,
                InteractionType::Input(String::from("BAD-EXPIRY")),
            )
            .with_second_attempt(SecondAttempt::new(
                InteractionType::Input(default::EXPIRY.to_string()),
                "Invalid date",
            )?)?,
        ),
        None,
        None,
        None,
        None,
    )?;

    identity.run(&SubCommand::New, Vec::new())?;
    Ok(())
}

#[test]
fn new_login() -> Result<(), Error> {
    let identity = Identity::new(
        "new_login",
        default_id_name(),
        None,
        None,
        None,
        Some(Interaction::new(
            prompt::LOGIN,
            InteractionType::Input(default::LOGIN.to_string()),
        )),
        None,
        None,
        None,
    )?;

    identity.run(&SubCommand::New, Vec::new())?;
    Ok(())
}

#[test]
fn new_origin() -> Result<(), Error> {
    let identity = Identity::new(
        "new_origin",
        default_id_name(),
        None,
        None,
        None,
        None,
        Some(Interaction::new(
            prompt::ORIGIN,
            InteractionType::Input(default::ORIGIN.to_string()),
        )),
        None,
        None,
    )?;

    identity.run(&SubCommand::New, Vec::new())?;
    Ok(())
}

#[test]
fn new_password() -> Result<(), Error> {
    let identity = Identity::new(
        "new_password",
        default_id_name(),
        None,
        None,
        None,
        None,
        None,
        Some(
            Interaction::new(
                prompt::PASSWORD,
                InteractionType::Password {
                    input: default::PASSWORD.to_string(),
                    confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
                },
            )
            .with_second_attempt(SecondAttempt::new(
                InteractionType::Password {
                    input: "Good-Password".to_string(),
                    confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
                },
                "Password mismatch",
            )?)?,
        ),
        None,
    )?;

    identity.run(&SubCommand::New, Vec::new())?;
    Ok(())
}

#[test]
fn edit_preserves_existing_expiry() -> Result<(), Error> {
    use std::{fs, process::Command};

    let config_dir = tempfile::tempdir()?;
    fs::write(
        config_dir.path().join("config.toml"),
        "colors = 'never'\n[author]\nlogin = ''",
    )?;

    let new_status = Command::new(env!("CARGO_BIN_EXE_pijul"))
        .env("PIJUL_CONFIG_DIR", config_dir.path())
        .args([
            "identity",
            "new",
            "--no-link",
            "--no-prompt",
            "my_identity",
            "--display-name",
            default::FULL_NAME,
            "--expiry",
            default::EXPIRY,
        ])
        .status()?;
    assert!(new_status.success());

    let edit_status = Command::new(env!("CARGO_BIN_EXE_pijul"))
        .env("PIJUL_CONFIG_DIR", config_dir.path())
        .args([
            "identity",
            "edit",
            "--no-link",
            "--no-prompt",
            "my_identity",
            "--email",
            default::EMAIL,
        ])
        .status()?;
    assert!(edit_status.success());

    let identity_path = config_dir
        .path()
        .join("identities")
        .join("my_identity")
        .join("identity.toml");
    let identity_data = fs::read_to_string(identity_path)?;
    let toml_data = identity_data.parse::<toml::Value>().unwrap();
    assert_eq!(
        toml_data.get("email").and_then(toml::Value::as_str),
        Some(default::EMAIL)
    );

    let public_expiry = toml_data
        .get("public_key")
        .and_then(|value| value.get("expires"))
        .and_then(toml::Value::as_str)
        .expect("expected public key expiry to be preserved")
        .parse::<jiff::Timestamp>()?;
    assert_eq!(
        public_expiry.strftime("%Y-%m-%d").to_string(),
        default::EXPIRY
    );

    let secret_key = fs::read_to_string(
        config_dir
            .path()
            .join("identities")
            .join("my_identity")
            .join("secret_key.json"),
    )?;
    let secret_key: pijul_core::key::SecretKey = serde_json::from_str(&secret_key)?;
    assert_eq!(
        secret_key
            .expires
            .expect("expected secret key expiry to be preserved")
            .strftime("%Y-%m-%d")
            .to_string(),
        default::EXPIRY
    );

    Ok(())
}

#[test]
fn edit_full() -> Result<(), Error> {
    let old_identity = Identity::new(
        "edit_full",
        default_id_name(),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )?;

    let new_identity = Identity::new(
        "edit_full",
        Interaction::new(
            prompt::ID_NAME,
            InteractionType::Input(String::from("new_id_name")),
        ),
        Some(default::FULL_NAME.to_string()),
        Some(
            Interaction::new(
                prompt::EMAIL,
                InteractionType::Input(String::from("BAD_EMAIL")),
            )
            .with_second_attempt(SecondAttempt::new(
                InteractionType::Input(default::EMAIL.to_string()),
                "Invalid email address",
            )?)?,
        ),
        Some(
            Interaction::new(
                prompt::EXPIRY_DATE,
                InteractionType::Input(String::from("BAD-EXPIRY")),
            )
            .with_second_attempt(SecondAttempt::new(
                InteractionType::Input(default::EXPIRY.to_string()),
                "Invalid date",
            )?)?,
        ),
        Some(Interaction::new(
            prompt::LOGIN,
            InteractionType::Input(default::LOGIN.to_string()),
        )),
        Some(Interaction::new(
            prompt::ORIGIN,
            InteractionType::Input(default::ORIGIN.to_string()),
        )),
        Some(
            Interaction::new(
                prompt::PASSWORD,
                InteractionType::Password {
                    input: default::PASSWORD.to_string(),
                    confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
                },
            )
            .with_second_attempt(SecondAttempt::new(
                InteractionType::Password {
                    input: "Good-Password".to_string(),
                    confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
                },
                "Password mismatch",
            )?)?,
        ),
        Some(Interaction::new(
            prompt::SELECT_KEY,
            InteractionType::Input(default::SSH.to_string()),
        )),
    )?;

    new_identity.run(
        &SubCommand::Edit(old_identity.id_name.valid_input().as_string()),
        vec![old_identity],
    )?;

    Ok(())
}

#[test]
fn edit_id_name() -> Result<(), Error> {
    let old_identity = Identity::new(
        "edit_id_name",
        default_id_name(),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )?;

    let new_identity = Identity::new(
        "edit_id_name",
        Interaction::new(
            prompt::ID_NAME,
            InteractionType::Input(String::from("new_id_name")),
        ),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )?;

    new_identity.run(
        &SubCommand::Edit(old_identity.id_name.valid_input().as_string()),
        vec![old_identity],
    )?;

    Ok(())
}

#[test]
fn edit_email() -> Result<(), Error> {
    let old_identity = Identity::new(
        "edit_email",
        default_id_name(),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )?;

    let new_identity = Identity::new(
        "edit_email",
        default_id_name(),
        None,
        Some(
            Interaction::new(
                prompt::EMAIL,
                InteractionType::Input(String::from("BAD_EMAIL")),
            )
            .with_second_attempt(SecondAttempt::new(
                InteractionType::Input(default::EMAIL.to_string()),
                "Invalid email address",
            )?)?,
        ),
        None,
        None,
        None,
        None,
        None,
    )?;

    new_identity.run(
        &SubCommand::Edit(old_identity.id_name.valid_input().as_string()),
        vec![old_identity],
    )?;

    Ok(())
}

#[test]
fn edit_expiry() -> Result<(), Error> {
    let old_identity = Identity::new(
        "edit_expiry",
        default_id_name(),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )?;

    let new_identity = Identity::new(
        "edit_expiry",
        default_id_name(),
        None,
        None,
        Some(
            Interaction::new(
                prompt::EXPIRY_DATE,
                InteractionType::Input(String::from("BAD-EXPIRY")),
            )
            .with_second_attempt(SecondAttempt::new(
                InteractionType::Input(default::EXPIRY.to_string()),
                "Invalid date",
            )?)?,
        ),
        None,
        None,
        None,
        None,
    )?;

    new_identity.run(
        &SubCommand::Edit(old_identity.id_name.valid_input().as_string()),
        vec![old_identity],
    )?;

    Ok(())
}

#[test]
fn edit_password() -> Result<(), Error> {
    let old_identity = Identity::new(
        "edit_password",
        default_id_name(),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )?;

    let new_identity = Identity::new(
        "edit_password",
        default_id_name(),
        None,
        None,
        None,
        None,
        None,
        Some(
            Interaction::new(
                prompt::PASSWORD,
                InteractionType::Password {
                    input: default::PASSWORD.to_string(),
                    confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
                },
            )
            .with_second_attempt(SecondAttempt::new(
                InteractionType::Password {
                    input: "Good-Password".to_string(),
                    confirm: Some(prompt::PASSWORD_REPROMPT.to_string()),
                },
                "Password mismatch",
            )?)?,
        ),
        None,
    )?;

    new_identity.run(
        &SubCommand::Edit(old_identity.id_name.valid_input().as_string()),
        vec![old_identity],
    )?;

    Ok(())
}

#[test]
fn remove() -> Result<(), Error> {
    let identity = Identity::new(
        "remove",
        default_id_name(),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    )?;

    identity.run(&SubCommand::Remove, vec![identity.clone()])?;
    Ok(())
}