// Golden fixture tests for MemoryStore
// Each test reads a golden JSON from contract/goldens/memory/ and verifies
// that the MoonBit implementation produces the expected result.
//
// All filesystem access is cross-target via moonbitlang/x/fs +
// moonbitlang/core/env, so these tests run on --target native too.

///| Monotonic counter for unique tmp dirs across same-ms calls.
let _gm_tmp_counter : Array[Int] = [0]

///| Make a temp directory with the given prefix. Uses $TMPDIR (or /tmp).
fn ffi_mk_temp_dir(prefix : String) -> String {
  let tmp = (@env.get_env_var("TMPDIR")).unwrap_or("/tmp")
  _gm_tmp_counter[0] = _gm_tmp_counter[0] + 1
  let suffix = @env.now().to_string() + "-" + _gm_tmp_counter[0].to_string()
  let dir = tmp + "/" + prefix + suffix
  let _ = try? @fs.create_dir(dir)

  dir
}

///| Recursive rmdir, best effort.
fn ffi_rm_rf(path : String) -> Unit {
  let _ = try? @fs.remove_dir(path)

}

///| Resolve path to a golden file. Honors MNEMO_CONTRACT_DIR; defaults
///| to `./contract` relative to the working directory.
fn ffi_golden_path(name : String) -> String {
  let dir = (@env.get_env_var("MNEMO_CONTRACT_DIR")).unwrap_or("./contract")
  dir + "/goldens/memory/" + name
}

///| Read the golden file as UTF-8 string; "" on any error.
fn ffi_read_golden_file(path : String) -> String {
  (try? @fs.read_file_to_string(path)).unwrap_or("")
}

///| Parse a golden JSON file and return as Json
fn load_golden(name : String) -> Json {
  let path = ffi_golden_path(name)
  let raw = ffi_read_golden_file(path)
  try {
    @json.parse(raw)
  } catch {
    _ => Json::null()
  }
}

///| Extract a string field from a Json object, or return default
fn json_get_str(
  obj : Map[String, Json],
  key : String,
  default~ : String = ""
) -> String {
  match obj.get(key) {
    Some(Json::String(s)) => s
    _ => default
  }
}

///| Extract an int field (from Number) from a Json object, or return default
fn json_get_int(
  obj : Map[String, Json],
  key : String,
  default~ : Int = 0
) -> Int {
  match obj.get(key) {
    Some(Json::Number(n, ..)) => n.to_int()
    _ => default
  }
}

// Golden 01: add with whitespace-only content → error_code "empty"
///|
async test "golden 01: add_trim_empty" {
  let golden = load_golden("01_add_trim_empty.json")
  match golden {
    Json::Object(g) => {
      let input = match g.get("input") {
        Some(Json::Object(o)) => o
        _ => fail("no input")
      }
      let expected = match g.get("expected") {
        Some(Json::Object(o)) => o
        _ => fail("no expected")
      }
      let content = json_get_str(input, "content")
      let dir = ffi_mk_temp_dir("mnemo-test-01-")
      let store = MemoryStore::load(dir)
      let result = store.add(Memory, content)
      ffi_rm_rf(dir)
      let expected_code = json_get_str(expected, "error_code")
      match result {
        AddErr(_, code) => assert_eq(code, expected_code)
        AddOk(_, _) => fail("expected AddErr(\{expected_code}), got AddOk")
      }
    }
    _ => fail("invalid golden JSON")
  }
}

// Golden 02: injection blocked
///|
async test "golden 02: injection_blocked" {
  let golden = load_golden("02_injection_blocked.json")
  match golden {
    Json::Object(g) => {
      let input = match g.get("input") {
        Some(Json::Object(o)) => o
        _ => fail("no input")
      }
      let expected = match g.get("expected") {
        Some(Json::Object(o)) => o
        _ => fail("no expected")
      }
      let content = json_get_str(input, "content")
      let dir = ffi_mk_temp_dir("mnemo-test-02-")
      let store = MemoryStore::load(dir)
      let result = store.add(Memory, content)
      ffi_rm_rf(dir)
      let expected_code = json_get_str(expected, "error_code")
      match result {
        AddErr(_, code) => assert_eq(code, expected_code)
        AddOk(_, _) => fail("expected AddErr(\{expected_code}), got AddOk")
      }
    }
    _ => fail("invalid golden JSON")
  }
}

// Golden 03: char budget overflow (max_chars=10)
///|
async test "golden 03: char_budget_overflow" {
  let golden = load_golden("03_char_budget_overflow.json")
  match golden {
    Json::Object(g) => {
      let input = match g.get("input") {
        Some(Json::Object(o)) => o
        _ => fail("no input")
      }
      let expected = match g.get("expected") {
        Some(Json::Object(o)) => o
        _ => fail("no expected")
      }
      let content = json_get_str(input, "content")
      let max_chars = json_get_int(input, "max_chars", default=2200)
      let dir = ffi_mk_temp_dir("mnemo-test-03-")
      let store = MemoryStore::load(dir, max_memory=max_chars)
      let result = store.add(Memory, content)
      ffi_rm_rf(dir)
      let expected_code = json_get_str(expected, "error_code")
      match result {
        AddErr(_, code) => assert_eq(code, expected_code)
        AddOk(_, _) => fail("expected AddErr(\{expected_code}), got AddOk")
      }
    }
    _ => fail("invalid golden JSON")
  }
}

// Golden 04: snapshot is frozen after add (snapshot taken at load time)
///|
async test "golden 04: frozen_snapshot" {
  let golden = load_golden("04_frozen_snapshot.json")
  match golden {
    Json::Object(g) => {
      let input = match g.get("input") {
        Some(Json::Object(o)) => o
        _ => fail("no input")
      }
      let expected = match g.get("expected") {
        Some(Json::Object(o)) => o
        _ => fail("no expected")
      }
      let content = json_get_str(input, "content")
      let dir = ffi_mk_temp_dir("mnemo-test-04-")
      let store = MemoryStore::load(dir)
      // Capture snapshot BEFORE add (at load time, should be "")
      let snap_before = store.snapshot(Memory)
      let result = store.add(Memory, content)
      match result {
        AddOk(_, _) => ()
        AddErr(msg, _) => fail("add failed: \{msg}")
      }
      // Snapshot should be unchanged (frozen at load time)
      let snap_after = store.snapshot(Memory)
      ffi_rm_rf(dir)
      // The golden says snapshot_unchanged: true
      let snapshot_unchanged = match expected.get("snapshot_unchanged") {
        Some(Json::True) => true
        _ => false
      }
      if snapshot_unchanged {
        assert_eq(snap_before, snap_after)
      }
    }
    _ => fail("invalid golden JSON")
  }
}

// Golden 05: replace by substring
///|
async test "golden 05: replace_substring" {
  let golden = load_golden("05_replace_substring.json")
  match golden {
    Json::Object(g) => {
      let input = match g.get("input") {
        Some(Json::Object(o)) => o
        _ => fail("no input")
      }
      let expected = match g.get("expected") {
        Some(Json::Object(o)) => o
        _ => fail("no expected")
      }
      let dir = ffi_mk_temp_dir("mnemo-test-05-")
      let store = MemoryStore::load(dir)
      // Setup: add initial entries
      match input.get("setup") {
        Some(Json::Array(arr)) =>
          for item in arr {
            match item {
              Json::String(s) => {
                let _ = store.add(Memory, s)
              }
              _ => ()
            }
          }
        _ => ()
      }
      let old = json_get_str(input, "old")
      let new_content = json_get_str(input, "new")
      let result = store.replace(Memory, old, new_content)
      ffi_rm_rf(dir)
      // Check op_result.ok
      match expected.get("op_result") {
        Some(Json::Object(op_result)) =>
          match op_result.get("ok") {
            Some(Json::True) =>
              match result {
                OpOk(_, _) => ()
                OpErr(msg, _) => fail("expected OpOk, got OpErr: \{msg}")
              }
            _ => ()
          }
        _ => ()
      }
      // Check entries
      match expected.get("entries") {
        Some(Json::Array(exp_entries)) => {
          let actual = store.list(Memory)
          assert_eq(actual.length(), exp_entries.length())
          for i, exp_item in exp_entries {
            match exp_item {
              Json::String(s) => assert_eq(actual[i], s)
              _ => ()
            }
          }
        }
        _ => ()
      }
    }
    _ => fail("invalid golden JSON")
  }
}

// Golden 06: remove by substring
///|
async test "golden 06: remove_substring" {
  let golden = load_golden("06_remove_substring.json")
  match golden {
    Json::Object(g) => {
      let input = match g.get("input") {
        Some(Json::Object(o)) => o
        _ => fail("no input")
      }
      let expected = match g.get("expected") {
        Some(Json::Object(o)) => o
        _ => fail("no expected")
      }
      let dir = ffi_mk_temp_dir("mnemo-test-06-")
      let store = MemoryStore::load(dir)
      // Setup: add initial entries
      match input.get("setup") {
        Some(Json::Array(arr)) =>
          for item in arr {
            match item {
              Json::String(s) => {
                let _ = store.add(Memory, s)
              }
              _ => ()
            }
          }
        _ => ()
      }
      let substring = json_get_str(input, "substring")
      let result = store.remove(Memory, substring)
      ffi_rm_rf(dir)
      // Check op_result.ok
      match expected.get("op_result") {
        Some(Json::Object(op_result)) =>
          match op_result.get("ok") {
            Some(Json::True) =>
              match result {
                OpOk(_, _) => ()
                OpErr(msg, _) => fail("expected OpOk, got OpErr: \{msg}")
              }
            _ => ()
          }
        _ => ()
      }
      // Check entries
      match expected.get("entries") {
        Some(Json::Array(exp_entries)) => {
          let actual = store.list(Memory)
          assert_eq(actual.length(), exp_entries.length())
          for i, exp_item in exp_entries {
            match exp_item {
              Json::String(s) => assert_eq(actual[i], s)
              _ => ()
            }
          }
        }
        _ => ()
      }
    }
    _ => fail("invalid golden JSON")
  }
}

// Golden 07: frozen snapshot is unchanged after additional adds
///|
async test "golden 07: frozen_snapshot_across_ops" {
  let golden = load_golden("07_frozen_snapshot_across_ops.json")
  match golden {
    Json::Object(g) => {
      let expected = match g.get("expected") {
        Some(Json::Object(o)) => o
        _ => fail("no expected")
      }
      let snapshot_frozen = match expected.get("snapshot_frozen_after_c") {
        Some(Json::True) => true
        _ => false
      }
      // Live replay: add "a" and "b", take snapshot, add "c", snapshot must be unchanged
      let dir = ffi_mk_temp_dir("mnemo-test-07-")
      let store = MemoryStore::load(dir)
      let _ = store.add(Memory, "entry a")
      let _ = store.add(Memory, "entry b")
      let snap2 = store.snapshot(Memory)
      let _ = store.add(Memory, "entry c")
      let snap3 = store.snapshot(Memory)
      ffi_rm_rf(dir)
      assert_eq(snap2 == snap3, snapshot_frozen)
    }
    _ => fail("invalid golden JSON")
  }
}

// Golden 08: char budget boundary — 45 + delimiter(3) + 56 = 104 > 100 → budget_exceeded
///|
async test "golden 08: char_budget_boundary" {
  let golden = load_golden("08_char_budget_boundary.json")
  match golden {
    Json::Object(g) => {
      let input = match g.get("input") {
        Some(Json::Object(o)) => o
        _ => fail("no input")
      }
      let expected = match g.get("expected") {
        Some(Json::Object(o)) => o
        _ => fail("no expected")
      }
      let limit = json_get_int(input, "limit", default=100)
      let first_len = json_get_int(input, "first_len", default=45)
      let second_len = json_get_int(input, "second_len", default=56)
      let first = "a".repeat(first_len)
      let second = "b".repeat(second_len)
      let dir = ffi_mk_temp_dir("mnemo-test-08-")
      let store = MemoryStore::load(dir, max_memory=limit)
      let r1 = store.add(Memory, first)
      let r2 = store.add(Memory, second)
      ffi_rm_rf(dir)
      // first must succeed
      let first_ok = match expected.get("first_ok") {
        Some(Json::True) => true
        _ => false
      }
      match r1 {
        AddOk(_, _) => assert_eq(true, first_ok)
        AddErr(msg, _) => fail("expected first add to succeed, got: \{msg}")
      }
      // second must fail with budget_exceeded
      let second_error_code = json_get_str(expected, "second_error_code")
      match r2 {
        AddErr(_, code) => assert_eq(code, second_error_code)
        AddOk(_, _) => fail("expected second add to fail with \{second_error_code}, got AddOk")
      }
    }
    _ => fail("invalid golden JSON")
  }
}

// Golden 10: remove ambiguous match — two entries both match "foo" → error ambiguous_match
///|
async test "golden 10: remove_ambiguous_match" {
  let golden = load_golden("10_remove_ambiguous_match.json")
  match golden {
    Json::Object(g) => {
      let input = match g.get("input") {
        Some(Json::Object(o)) => o
        _ => fail("no input")
      }
      let expected = match g.get("expected") {
        Some(Json::Object(o)) => o
        _ => fail("no expected")
      }
      let dir = ffi_mk_temp_dir("mnemo-test-10-")
      let store = MemoryStore::load(dir)
      // Setup: add initial entries
      match input.get("setup") {
        Some(Json::Array(arr)) =>
          for item in arr {
            match item {
              Json::String(s) => {
                let _ = store.add(Memory, s)
              }
              _ => ()
            }
          }
        _ => ()
      }
      let substring = json_get_str(input, "substring")
      let result = store.remove(Memory, substring)
      // Check op_result: must be error with ambiguous_match
      match expected.get("op_result") {
        Some(Json::Object(op_result)) =>
          match op_result.get("ok") {
            Some(Json::False) => {
              let expected_code = json_get_str(op_result, "error_code")
              match result {
                OpErr(_, code) => assert_eq(code, expected_code)
                OpOk(_, _) =>
                  fail("expected OpErr(\{expected_code}), got OpOk")
              }
            }
            _ => ()
          }
        _ => ()
      }
      // Check entries remain unchanged
      match expected.get("entries") {
        Some(Json::Array(exp_entries)) => {
          let actual = store.list(Memory)
          assert_eq(actual.length(), exp_entries.length())
          for i, exp_item in exp_entries {
            match exp_item {
              Json::String(s) => assert_eq(actual[i], s)
              _ => ()
            }
          }
        }
        _ => ()
      }
      ffi_rm_rf(dir)
    }
    _ => fail("invalid golden JSON")
  }
}

// Golden 09: exhaustive injection patterns — all 13 must be blocked
///|
async test "golden 09: injection_exhaustive" {
  let golden = load_golden("09_injection_exhaustive.json")
  match golden {
    Json::Object(g) => {
      let expected = match g.get("expected") {
        Some(Json::Object(o)) => o
        _ => fail("no expected")
      }
      let all_blocked = match expected.get("all_blocked") {
        Some(Json::True) => true
        _ => false
      }
      // The cases array carries all 13 inputs; replay each
      match expected.get("cases") {
        Some(Json::Array(cases)) => {
          let mut all_ok = true
          for c in cases {
            match c {
              Json::Object(obj) => {
                let input_prefix = json_get_str(obj, "input_prefix")
                let blocked = match obj.get("blocked") {
                  Some(Json::True) => true
                  _ => false
                }
                if !blocked {
                  all_ok = false
                }
                // Live check: the pattern_id-named cases must all be blocked
                let dir = ffi_mk_temp_dir("mnemo-test-09-")
                let store = MemoryStore::load(dir)
                let r = store.add(Memory, input_prefix)
                ffi_rm_rf(dir)
                match r {
                  AddErr(_, code) =>
                    assert_eq(code, "injection_blocked")
                  AddOk(_, _) =>
                    fail("golden 09: expected blocked for \{input_prefix}, got AddOk")
                }
              }
              _ => ()
            }
          }
          assert_eq(all_ok, all_blocked)
        }
        _ => fail("no cases array in golden 09")
      }
    }
    _ => fail("invalid golden JSON")
  }
}