//! Manifest.lock writer tests.
//!
//! Phase 2 of plan 21. Renders a [`Lockfile`] and re-evaluates the
//! result in an mlua VM to confirm the output is valid Lua with the
//! expected shape.

use std::collections::BTreeMap;

use assay::install::lock::{LockExtension, LockLib, Lockfile};
use mlua::{Lua, Table};

fn render(lf: &Lockfile) -> String {
    lf.to_lua()
}

#[test]
fn renders_minimal_empty_lockfile() {
    let lf = Lockfile {
        assay: None,
        extensions: vec![],
        libs: vec![],
    };
    let text = render(&lf);

    let lua = Lua::new();
    let t: Table = lua.load(&text).eval().unwrap();
    assert!(t.get::<Option<String>>("assay").unwrap().is_none());
    let exts: Table = t.get("extensions").unwrap();
    assert_eq!(exts.len().unwrap(), 0);
    let libs: Table = t.get("libs").unwrap();
    assert_eq!(libs.len().unwrap(), 0);
}

#[test]
fn renders_full_lockfile_round_trippable_in_lua() {
    let mut sha = BTreeMap::new();
    sha.insert("aarch64".into(), "bbbb".into());
    sha.insert("x86_64".into(), "aaaa".into());

    let lf = Lockfile {
        assay: Some("0.15.6".into()),
        extensions: vec![LockExtension {
            name: "assay-engine".into(),
            version: "0.4.1".into(),
            url: "https://example.com/assay-engine-0.4.1-x86_64.tar.gz".into(),
            sha256: sha,
        }],
        libs: vec![LockLib {
            name: "sysops".into(),
            version: "0.1.0".into(),
            url: "https://example.com/sysops-0.1.0.tar.gz".into(),
            sha256: "cccc".into(),
        }],
    };

    let text = render(&lf);
    let lua = Lua::new();
    let t: Table = lua.load(&text).eval().unwrap();

    assert_eq!(t.get::<String>("assay").unwrap(), "0.15.6");

    let exts: Table = t.get("extensions").unwrap();
    let e1: Table = exts.get(1).unwrap();
    assert_eq!(e1.get::<String>("name").unwrap(), "assay-engine");
    assert_eq!(e1.get::<String>("version").unwrap(), "0.4.1");
    assert_eq!(
        e1.get::<String>("url").unwrap(),
        "https://example.com/assay-engine-0.4.1-x86_64.tar.gz"
    );
    let sha_t: Table = e1.get("sha256").unwrap();
    assert_eq!(sha_t.get::<String>("x86_64").unwrap(), "aaaa");
    assert_eq!(sha_t.get::<String>("aarch64").unwrap(), "bbbb");

    let libs: Table = t.get("libs").unwrap();
    let l1: Table = libs.get(1).unwrap();
    assert_eq!(l1.get::<String>("name").unwrap(), "sysops");
    assert_eq!(l1.get::<String>("sha256").unwrap(), "cccc");
}

#[test]
fn sha256_keys_are_emitted_sorted_for_stable_diffs() {
    let mut sha = BTreeMap::new();
    sha.insert("zzzz".into(), "9".into());
    sha.insert("aaaa".into(), "1".into());
    sha.insert("mmmm".into(), "5".into());

    let lf = Lockfile {
        assay: None,
        extensions: vec![LockExtension {
            name: "x".into(),
            version: "1".into(),
            url: "u".into(),
            sha256: sha,
        }],
        libs: vec![],
    };

    let text = render(&lf);
    let aaaa = text.find("aaaa").expect("missing aaaa");
    let mmmm = text.find("mmmm").expect("missing mmmm");
    let zzzz = text.find("zzzz").expect("missing zzzz");
    assert!(aaaa < mmmm && mmmm < zzzz, "sha256 keys not sorted: {text}");
}

#[test]
fn renders_includes_do_not_edit_header() {
    let lf = Lockfile {
        assay: None,
        extensions: vec![],
        libs: vec![],
    };
    let text = render(&lf);
    assert!(text.starts_with("-- Generated by `assay install`."));
}

#[test]
fn lua_strings_with_quotes_and_backslashes_are_escaped() {
    let lf = Lockfile {
        assay: None,
        extensions: vec![],
        libs: vec![LockLib {
            name: r#"weird"name\with-stuff"#.into(),
            version: "1".into(),
            url: "u".into(),
            sha256: "sha".into(),
        }],
    };
    let text = render(&lf);
    // round-trip through Lua: if escaping is wrong, the lua eval errors
    // or the deserialised name differs.
    let lua = Lua::new();
    let t: Table = lua.load(&text).eval().unwrap();
    let libs: Table = t.get("libs").unwrap();
    let l1: Table = libs.get(1).unwrap();
    let name: String = l1.get("name").unwrap();
    assert_eq!(name, r#"weird"name\with-stuff"#);
}
