use shackles_core::Provenance;
use shackles_git_hooks_adapter::{
    GitHooksAdapterRequirement, HookKind, ItemRequirements, ProductId, ScalarAssertion,
    TextFileContents, TextFileRequirements, lower_git_hooks_requirement,
};

#[test]
fn lowering_preserves_two_policy_assertions() -> Result<(), String> {
    let main = contents("main")?;
    let fragment = contents("fragment")?;
    let requirement = GitHooksAdapterRequirement::new(
        HookKind::PreCommit,
        ProductId::new("shakrs").map_err(|err| err.to_string())?,
        ItemRequirements {
            required: vec![(main.clone(), "main message".to_owned())],
            forbidden: Vec::new(),
            closed: None,
        },
        ScalarAssertion::Equals(fragment.clone(), "fragment message".to_owned()),
    )
    .map_err(|err| err.to_string())?;

    let output = lower_git_hooks_requirement(
        Provenance {
            policy: "policy".to_owned(),
        },
        requirement,
    )
    .map_err(|err| format!("{err:?}"))?;

    assert_eq!(output.len(), 2, "Lowering must emit exactly two files.");
    assert_eq!(
        output[0].slot.as_str(),
        "git-hook-pre-commit",
        "The main slot must remain stable."
    );
    let main_req = output[0]
        .requirement
        .as_any()
        .downcast_ref::<TextFileRequirements>()
        .ok_or_else(|| "The main output must be a text requirement.".to_owned())?;
    assert_eq!(
        main_req.contents.required[0].0, main,
        "Main bytes must be preserved."
    );
    assert!(
        main_req.exact_contents.is_none(),
        "The main file must use containment."
    );

    let fragment_req = output[1]
        .requirement
        .as_any()
        .downcast_ref::<TextFileRequirements>()
        .ok_or_else(|| "The fragment output must be a text requirement.".to_owned())?;
    assert!(
        matches!(&fragment_req.exact_contents, Some(ScalarAssertion::Equals(value, message)) if value == &fragment && message == "fragment message"),
        "The fragment assertion must be preserved."
    );
    assert!(
        fragment_req.contents.required.is_empty(),
        "The fragment must not contain item requirements."
    );
    Ok(())
}

fn contents(value: &str) -> Result<TextFileContents, String> {
    TextFileContents::new(value.as_bytes().to_vec()).map_err(|err| err.to_string())
}
