#include <vereign/sqlite/connection.hh>

#include <vereign/sqlite/errors.hh>
#include <vereign/core/scope_guard.hh>

#include <fmt/format.h>
#include <sqlite3.h>
#include <iostream>

namespace vereign::sqlite {

Connection::Connection(const std::string& path)
  : db_{nullptr}
{
  auto rc = sqlite3_open(path.data(), &db_);
  if (rc != SQLITE_OK) {
    throw Error{rc, fmt::format("open db failed, err: {}", sqlite3_errmsg(db_))};
  }
}

Connection::~Connection() noexcept {
  sqlite3_close(db_);
}

void Connection::BeginExplicitTransaction() {
  char* errMsg = nullptr;
  auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
  auto rc = sqlite3_exec(db_, "BEGIN EXCLUSIVE", nullptr, nullptr, &errMsg);
  if (rc != SQLITE_OK) {
    throw Error(rc, fmt::format("starting transaction failed, err: {}", errMsg));
  }
}

void Connection::Commit() {
  char* errMsg = nullptr;
  auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });

  auto rc = sqlite3_exec(db_, "COMMIT", nullptr, nullptr, &errMsg);
  if (rc != SQLITE_OK) {
    throw Error(rc, fmt::format("commit transaction failed, err: {}", errMsg));
  }
}

void Connection::Rollback() {
  char* errMsg = nullptr;
  auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });

  auto rc = sqlite3_exec(db_, "ROLLBACK", nullptr, nullptr, &errMsg);
  if (rc != SQLITE_OK) {
    throw Error(rc, fmt::format("rollback transaction failed, err: {}", errMsg));
  }
}

void Connection::Execute(const std::string& sql) {
  char* errMsg = nullptr;
  auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });

  auto rc = sqlite3_exec(db_, sql.data(), nullptr, nullptr, &errMsg);
  if (rc != SQLITE_OK) {
    throw Error(rc, fmt::format("query execution failed, err: {}", errMsg));
  }
}

auto Connection::Prepare(const std::string& sql) -> Statement {
  sqlite3_stmt *stmt = nullptr;

  auto rc = sqlite3_prepare_v2(db_, sql.data(), -1, &stmt, nullptr);
  if (rc != SQLITE_OK) {
    throw Error(rc, fmt::format("preparing statement failed, err: {}", sqlite3_errmsg(db_)));
  }

  return Statement{db_, stmt};
}

} // namespace vereign::sqlite