// CRUD: put/get/del with write flags
// Textbook section: Volume I, chapter 4, "Basic CRUD operations",
//   sections "put/get/del/replace", "Write flags".
// What it demonstrates: NOOVERWRITE (insert_unique) and MDBX_KEYEXIST as an expected code;
//   UPSERT; CURRENT (update); deletion; MDBX_NOTFOUND as an expected code.
// Build: g++ -std=c++17 -I<libmdbx-include> -I../common 03-crud.c++ -lmdbx
// Run: ./03-crud
// Expected output:
//   insert duplicate -> MDBX_KEYEXIST as expected
//   try_insert duplicate -> done=0
//   after upsert: value2
//   update existing: value3
//   update absent -> MDBX_NOTFOUND as expected
//   erase(key) -> removed=true
//   get(after erase) -> MDBX_NOTFOUND as expected
//   ok: NOOVERWRITE/UPSERT/CURRENT; KEYEXIST/NOTFOUND handled explicitly
// On failure — message to stderr and exit 1.
//
// SPDX-License-Identifier: Apache-2.0

#include <iostream>
#include <string>

#include "common.h++"
#include <mdbx.h++>

int main() {
  try {
    const std::string path = std::string(example::tmpdir()) + "/mdbx-03-crud.mdbx";
    mdbx::env::remove(path);

    mdbx::env_managed env(path, mdbx::env_managed::create_parameters(), mdbx::env::operate_parameters());
    auto txn = env.start_write();
    auto table = txn.open_map(nullptr);

    // NOOVERWRITE: inserting a unique key.
    txn.insert(table, mdbx::slice("key"), mdbx::slice("value1"));

    // Re-inserting the same key -> key_exists (MDBX_KEYEXIST).
    try {
      txn.insert(table, mdbx::slice("key"), mdbx::slice("value2"));
      std::cerr << "FAIL: duplicate insert did not throw\n";
      return EXIT_FAILURE;
    } catch (const mdbx::key_exists &) {
      std::cout << "insert duplicate -> MDBX_KEYEXIST as expected\n";
    }

    // try_insert does not throw; it reports the result via the done flag.
    auto ins = txn.try_insert(table, mdbx::slice("key"), mdbx::slice("value2"));
    std::cout << "try_insert duplicate -> done=" << (ins.done ? 1 : 0) << "\n";

    // UPSERT: insert or overwrite.
    txn.upsert(table, mdbx::slice("key"), mdbx::slice("value2"));
    std::cout << "after upsert: " << txn.get(table, mdbx::slice("key")).as_string() << "\n";

    // CURRENT (update): update only an existing key.
    txn.update(table, mdbx::slice("key"), mdbx::slice("value3"));
    std::cout << "update existing: " << txn.get(table, mdbx::slice("key")).as_string() << "\n";
    try {
      txn.update(table, mdbx::slice("absent"), mdbx::slice("x"));
      std::cerr << "FAIL: update of absent key did not throw\n";
      return EXIT_FAILURE;
    } catch (const mdbx::not_found &) {
      std::cout << "update absent -> MDBX_NOTFOUND as expected\n";
    }

    // Deletion by key.
    const bool removed = txn.erase(table, mdbx::slice("key"));
    std::cout << "erase(key) -> removed=" << (removed ? "true" : "false") << "\n";
    try {
      (void)txn.get(table, mdbx::slice("key"));
      std::cerr << "FAIL: get after erase did not throw\n";
      return EXIT_FAILURE;
    } catch (const mdbx::not_found &) {
      std::cout << "get(after erase) -> MDBX_NOTFOUND as expected\n";
    }

    txn.commit();

    std::cout << "ok: NOOVERWRITE/UPSERT/CURRENT; KEYEXIST/NOTFOUND handled explicitly\n";
    return EXIT_SUCCESS;
  } catch (const std::exception &ex) {
    std::cerr << "error: " << ex.what() << "\n";
    return EXIT_FAILURE;
  }
}