#include "storage/site_store.hpp"

#include "storage/database.hpp"
#include "util/id_util.hpp"
#include "util/string_util.hpp"

#include <sqlite3.h>

namespace lm {

namespace {

constexpr const char* kDefaultSiteId = "site_configuration";
constexpr const char* kDefaultSiteName = "ConfigurationSite";

nlohmann::json rowToJson(sqlite3_stmt* stmt)
{
  auto textOrNull = [&](int col) -> nlohmann::json {
    if (sqlite3_column_type(stmt, col) == SQLITE_NULL)
      return nullptr;
    return nlohmann::json(reinterpret_cast<const char*>(sqlite3_column_text(stmt, col)));
  };
  return {{"id", textOrNull(0)},
          {"name", textOrNull(1)},
          {"created_at", textOrNull(2)},
          {"updated_at", textOrNull(3)}};
}

}  // namespace

SiteStore::SiteStore(Database& db) : db_(db) {}

std::string SiteStore::ensureDefaultSiteId()
{
  std::lock_guard<std::mutex> lock(mu_);
  sqlite3_stmt* stmt = nullptr;
  if (sqlite3_prepare_v2(db_.handle(), "SELECT id FROM sites WHERE id = ?1", -1, &stmt, nullptr) != SQLITE_OK)
    return kDefaultSiteId;
  sqlite3_bind_text(stmt, 1, kDefaultSiteId, -1, SQLITE_STATIC);
  bool exists = sqlite3_step(stmt) == SQLITE_ROW;
  sqlite3_finalize(stmt);
  if (exists)
    return kDefaultSiteId;

  const std::string now = IdUtil::nowIso8601();
  if (sqlite3_prepare_v2(db_.handle(),
                         "INSERT INTO sites(id, name, created_at, updated_at) VALUES(?1,?2,?3,?4)",
                         -1,
                         &stmt,
                         nullptr) != SQLITE_OK)
    return kDefaultSiteId;
  sqlite3_bind_text(stmt, 1, kDefaultSiteId, -1, SQLITE_STATIC);
  sqlite3_bind_text(stmt, 2, kDefaultSiteName, -1, SQLITE_STATIC);
  sqlite3_bind_text(stmt, 3, now.c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(stmt, 4, now.c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_step(stmt);
  sqlite3_finalize(stmt);
  return kDefaultSiteId;
}

nlohmann::json SiteStore::list() const
{
  std::lock_guard<std::mutex> lock(mu_);
  nlohmann::json sites = nlohmann::json::array();
  sqlite3_stmt* stmt = nullptr;
  if (sqlite3_prepare_v2(db_.handle(),
                         "SELECT id, name, created_at, updated_at FROM sites ORDER BY name",
                         -1,
                         &stmt,
                         nullptr) != SQLITE_OK)
    return sites;
  while (sqlite3_step(stmt) == SQLITE_ROW)
    sites.push_back(rowToJson(stmt));
  sqlite3_finalize(stmt);
  return sites;
}

std::optional<nlohmann::json> SiteStore::find(const std::string& id) const
{
  std::lock_guard<std::mutex> lock(mu_);
  sqlite3_stmt* stmt = nullptr;
  if (sqlite3_prepare_v2(db_.handle(),
                         "SELECT id, name, created_at, updated_at FROM sites WHERE id = ?1",
                         -1,
                         &stmt,
                         nullptr) != SQLITE_OK)
    return std::nullopt;
  sqlite3_bind_text(stmt, 1, id.c_str(), -1, SQLITE_TRANSIENT);
  std::optional<nlohmann::json> out;
  if (sqlite3_step(stmt) == SQLITE_ROW)
    out = rowToJson(stmt);
  sqlite3_finalize(stmt);
  return out;
}

std::optional<nlohmann::json> SiteStore::create(const nlohmann::json& payload, std::string& err)
{
  if (!payload.is_object())
  {
    err = "payload must be an object";
    return std::nullopt;
  }
  const std::string name = StringUtil::trimCopy(payload.value("name", ""));
  if (name.empty())
  {
    err = "name is required";
    return std::nullopt;
  }

  const std::string id = payload.value("id", IdUtil::newId());
  const std::string now = IdUtil::nowIso8601();

  std::lock_guard<std::mutex> lock(mu_);
  sqlite3_stmt* stmt = nullptr;
  if (sqlite3_prepare_v2(db_.handle(),
                         "INSERT INTO sites(id, name, created_at, updated_at) VALUES(?1,?2,?3,?4)",
                         -1,
                         &stmt,
                         nullptr) != SQLITE_OK)
  {
    err = sqlite3_errmsg(db_.handle());
    return std::nullopt;
  }
  sqlite3_bind_text(stmt, 1, id.c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(stmt, 2, name.c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(stmt, 3, now.c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(stmt, 4, now.c_str(), -1, SQLITE_TRANSIENT);
  if (sqlite3_step(stmt) != SQLITE_DONE)
  {
    err = sqlite3_errmsg(db_.handle());
    sqlite3_finalize(stmt);
    return std::nullopt;
  }
  sqlite3_finalize(stmt);

  return nlohmann::json{{"id", id}, {"name", name}, {"created_at", now}, {"updated_at", now}};
}

bool SiteStore::update(const std::string& id, const nlohmann::json& payload, std::string& err)
{
  auto existing = find(id);
  if (!existing)
  {
    err = "site not found";
    return false;
  }

  nlohmann::json merged = *existing;
  if (payload.contains("name"))
    merged["name"] = payload["name"];

  const std::string now = IdUtil::nowIso8601();
  std::lock_guard<std::mutex> lock(mu_);
  sqlite3_stmt* stmt = nullptr;
  if (sqlite3_prepare_v2(db_.handle(),
                         "UPDATE sites SET name = ?2, updated_at = ?3 WHERE id = ?1",
                         -1,
                         &stmt,
                         nullptr) != SQLITE_OK)
  {
    err = sqlite3_errmsg(db_.handle());
    return false;
  }
  sqlite3_bind_text(stmt, 1, id.c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(stmt, 2, merged.value("name", "").c_str(), -1, SQLITE_TRANSIENT);
  sqlite3_bind_text(stmt, 3, now.c_str(), -1, SQLITE_TRANSIENT);
  const bool ok = sqlite3_step(stmt) == SQLITE_DONE;
  if (!ok)
    err = sqlite3_errmsg(db_.handle());
  sqlite3_finalize(stmt);
  return ok;
}

bool SiteStore::remove(const std::string& id, std::string& err)
{
  std::lock_guard<std::mutex> lock(mu_);

  sqlite3_stmt* check = nullptr;
  if (sqlite3_prepare_v2(db_.handle(), "SELECT id FROM sites WHERE id = ?1", -1, &check, nullptr) != SQLITE_OK)
  {
    err = sqlite3_errmsg(db_.handle());
    return false;
  }
  sqlite3_bind_text(check, 1, id.c_str(), -1, SQLITE_TRANSIENT);
  if (sqlite3_step(check) != SQLITE_ROW)
  {
    sqlite3_finalize(check);
    err = "site not found";
    return false;
  }
  sqlite3_finalize(check);

  if (sqlite3_prepare_v2(db_.handle(),
                         "SELECT COUNT(*) FROM maps WHERE site_id = ?1",
                         -1,
                         &check,
                         nullptr) != SQLITE_OK)
  {
    err = sqlite3_errmsg(db_.handle());
    return false;
  }
  sqlite3_bind_text(check, 1, id.c_str(), -1, SQLITE_TRANSIENT);
  int map_count = 0;
  if (sqlite3_step(check) == SQLITE_ROW)
    map_count = sqlite3_column_int(check, 0);
  sqlite3_finalize(check);
  if (map_count > 0)
  {
    err = "site has maps and cannot be deleted";
    return false;
  }

  if (sqlite3_prepare_v2(db_.handle(), "SELECT COUNT(*) FROM sites", -1, &check, nullptr) != SQLITE_OK)
  {
    err = sqlite3_errmsg(db_.handle());
    return false;
  }
  int site_count = 0;
  if (sqlite3_step(check) == SQLITE_ROW)
    site_count = sqlite3_column_int(check, 0);
  sqlite3_finalize(check);
  if (site_count <= 1)
  {
    err = "cannot delete the last site";
    return false;
  }

  sqlite3_stmt* stmt = nullptr;
  if (sqlite3_prepare_v2(db_.handle(), "DELETE FROM sites WHERE id = ?1", -1, &stmt, nullptr) != SQLITE_OK)
  {
    err = sqlite3_errmsg(db_.handle());
    return false;
  }
  sqlite3_bind_text(stmt, 1, id.c_str(), -1, SQLITE_TRANSIENT);
  const bool ok = sqlite3_step(stmt) == SQLITE_DONE;
  if (!ok)
    err = sqlite3_errmsg(db_.handle());
  sqlite3_finalize(stmt);
  return ok;
}

}  // namespace lm
