use std::collections::BTreeSet;
mod common;
use common::Environment;
use common::Result;

fn create_environment() -> Result<(Environment, String)> {
    Environment::scooby_gang_bootstrap()
}

// The keys for the different authorizations in the JSON file.
const SIGN_COMMIT: &str = "sign_commit";
const SIGN_TAG: &str = "sign_tag";
const SIGN_ARCHIVE: &str = "sign_archive";
const ADD_USER: &str = "add_user";
const RETIRE_USER: &str = "retire_user";
const AUDIT: &str = "audit";

const CAPS: &[&str] = &[
    SIGN_COMMIT,
    SIGN_TAG,
    SIGN_ARCHIVE,
    ADD_USER,
    RETIRE_USER,
    AUDIT
];

fn check(e: &Environment, args: &[&str], expected_caps: &[&str])
{
    let petname = e.buffy.petname;
    let fpr = e.buffy.fingerprint.to_string();

    let openpgp_policy_toml = e.git_state().join("openpgp-policy.toml");
    if let Err(err) = std::fs::remove_file(&openpgp_policy_toml) {
        if std::io::ErrorKind::NotFound != err.kind() {
            panic!("Removing {}", openpgp_policy_toml.display());
        }
    }

    let mut sq_git: Vec<&str> = [
        "policy",
        "authorize",
        &petname,
        &fpr,
    ].to_vec();
    sq_git.extend(args);

    eprintln!("Running: sq-git {}", sq_git.join(" "));

    e.sq_git(&sq_git).unwrap();

    let output = e.sq_git(&[
        "policy",
        "describe",
        "--output-format", "json"
    ]).unwrap();

    eprintln!("Output:\n{}", String::from_utf8_lossy(&output.stdout));

    let status: serde_json::Value = serde_json::from_slice(&output.stdout)
        .expect("\"sq policy describe\" emits valid json output");
    // eprintln!("JSON:\n{:?}", status);

    let auths = &status["authorization"];
    // eprintln!("JSON[\"authorization\"]:\n{:?}", auths);

    let user = &auths[petname];
    eprintln!("JSON[\"authorization\"][\"{}\"]:\n{:?}", petname, user);

    let caps = BTreeSet::from_iter(CAPS.iter());

    let expected_caps_present = BTreeSet::from_iter(expected_caps.iter());
    for cap in expected_caps_present.iter() {
        eprintln!("Checking that {} is true", cap);
        match &user[cap] {
            serde_json::Value::Bool(true) => (),
            v => {
                panic!("expected {} to be true, but it is: {:?}",
                       cap, v);
            }
        }
    }

    let expected_caps_missing = caps.difference(&expected_caps_present);
    for cap in expected_caps_missing {
        eprintln!("Checking that {} is not set or false", cap);
        match &user[cap] {
            serde_json::Value::Null => (),
            serde_json::Value::Bool(false) => (),
            v => {
                panic!("expected {} to be false, but it is: {:?}",
                       cap, v);
            }
        }
    }
}

#[test]
fn check_flags() -> anyhow::Result<()> {
    let (e, _root) = create_environment()?;

    // One at a time.
    check(&e,
          &["--sign-commit"],
          &[ SIGN_COMMIT ]);

    check(&e,
          &["--sign-archive"],
          &[ SIGN_ARCHIVE ]);

    check(&e,
          &["--sign-tag"],
          &[ SIGN_TAG ]);

    check(&e,
          &["--add-user"],
          &[ ADD_USER ]);

    check(&e,
          &["--retire-user"],
          &[ RETIRE_USER ]);

    check(&e,
          &["--audit"],
          &[ AUDIT ]);


    // Mix and match.
    check(&e,
          &["--sign-commit", "--sign-archive"],
          &[ SIGN_COMMIT, SIGN_ARCHIVE ]);

    check(&e,
          &["--sign-tag", "--add-user", "--retire-user"],
          &[ SIGN_TAG, ADD_USER, RETIRE_USER ]);


    // Add some negatives.  The last positive or negative wins.

    check(&e,
          &["--no-sign-commit", "--sign-archive"],
          &[ SIGN_ARCHIVE ]);

    check(&e,
          &["--sign-commit", "--no-sign-commit", "--sign-archive"],
          &[ SIGN_ARCHIVE ]);

    check(&e,
          &["--no-sign-commit", "--sign-commit", "--sign-archive"],
          &[ SIGN_COMMIT, SIGN_ARCHIVE ]);


    // The meta-capabilities.
    check(&e,
          &["--committer"],
          &[ SIGN_COMMIT ]);

    check(&e,
          &["--release-manager"],
          &[ SIGN_COMMIT, SIGN_TAG, SIGN_ARCHIVE ]);

    check(&e,
          &["--project-maintainer"],
          &[ SIGN_COMMIT, SIGN_TAG, SIGN_ARCHIVE, ADD_USER, RETIRE_USER,
             AUDIT ]);

    // Union.
    check(&e,
          &["--project-maintainer", "--committer"],
          &[ SIGN_COMMIT, SIGN_TAG, SIGN_ARCHIVE, ADD_USER, RETIRE_USER,
             AUDIT ]);

    check(&e,
          &["--project-maintainer", "--no-sign-archive"],
          &[ SIGN_COMMIT, SIGN_TAG, ADD_USER, RETIRE_USER, AUDIT ]);

    // A meta-capability does not trump a negative capability.
    check(&e,
          &["--no-sign-archive", "--project-maintainer"],
          &[ SIGN_COMMIT, SIGN_TAG, ADD_USER, RETIRE_USER, AUDIT ]);

    Ok(())
}
