#include "storage/state_repository.hpp"

#include "domain/layout_profile.hpp"
#include "domain/layout_schema.hpp"
#include "storage/database.hpp"
#include "util/file_util.hpp"
#include "util/id_util.hpp"
#include "util/string_util.hpp"

namespace lm {

std::filesystem::path StateRepository::modelsDir() const
{
  return app_.data_path.parent_path() / "models";
}

std::filesystem::path StateRepository::profileFilePath(const std::string& id) const
{
  return modelsDir() / (id + ".json");
}

std::optional<nlohmann::json> StateRepository::loadProfileFromDisk(const std::string& id) const
{
  if (auto profile = db_.getLayoutProfile(id))
    return profile;
  const auto raw = FileUtil::readBinary(profileFilePath(id));
  if (raw.empty())
    return std::nullopt;
  try
  {
    return nlohmann::json::parse(raw);
  }
  catch (...)
  {
    return std::nullopt;
  }
}

bool StateRepository::saveProfileToDisk(const nlohmann::json& profile) const
{
  if (!profile.is_object() || !profile.contains("id") || !profile["id"].is_string())
    return false;
  return db_.setLayoutProfile(profile);
}

bool StateRepository::deleteProfileFile(const std::string& id) const
{
  db_.deleteLayoutProfile(id);
  std::error_code ec;
  std::filesystem::remove(profileFilePath(id), ec);
  return true;
}

void StateRepository::loadActiveCache()
{
  nlohmann::json& state = app_.state;
  const auto idx = LayoutProfile::findActiveIndex(state);
  if (!idx)
    return;

  const std::string id = state["layouts"][*idx]["id"].get<std::string>();
  nlohmann::json profile;
  if (auto loaded = loadProfileFromDisk(id))
    profile = *loaded;
  else
  {
    profile = LayoutProfile::make(state["layouts"][*idx]["name"].get<std::string>(),
                                  LayoutSchema::defaultLayoutObject(),
                                  nlohmann::json::array());
    profile["id"] = id;
    if (state["layouts"][*idx].contains("created_at"))
      profile["created_at"] = state["layouts"][*idx]["created_at"];
  }

  if (!profile.contains("layout") || !profile["layout"].is_object())
    profile["layout"] = LayoutSchema::defaultLayoutObject();
  if (!profile.contains("lidars") || !profile["lidars"].is_array())
    profile["lidars"] = nlohmann::json::array();
  if (!profile.contains("imus") || !profile["imus"].is_array())
    profile["imus"] = nlohmann::json::array();

  LayoutSchema::ensure(profile["layout"]);
  state["layout"] = profile["layout"];
  state["lidars"] = profile["lidars"];
  state["imus"] = profile["imus"];
}

bool StateRepository::persistActiveProfile()
{
  nlohmann::json& state = app_.state;
  const auto idx = LayoutProfile::findActiveIndex(state);
  if (!idx)
    return false;

  auto& entry = state["layouts"][*idx];
  nlohmann::json profile;
  profile["id"] = entry["id"];
  profile["name"] = entry.contains("name") ? entry["name"] : nlohmann::json("Layout");
  profile["created_at"] = entry.value("created_at", IdUtil::nowIso8601());
  profile["updated_at"] = IdUtil::nowIso8601();
  profile["layout"] = state["layout"];
  profile["lidars"] = state["lidars"];
  profile["imus"] = state.contains("imus") && state["imus"].is_array() ? state["imus"] : nlohmann::json::array();
  LayoutSchema::ensure(profile["layout"]);
  if (!saveProfileToDisk(profile))
    return false;
  entry = LayoutProfile::catalogEntryFromProfile(profile);
  return true;
}

nlohmann::json StateRepository::globalStateForDisk(const nlohmann::json& state) const
{
  nlohmann::json out = nlohmann::json::object();
  out["version"] = 3;
  if (state.contains("active_layout_id"))
    out["active_layout_id"] = state["active_layout_id"];
  out["layouts"] = nlohmann::json::array();
  if (state.contains("layouts") && state["layouts"].is_array())
  {
    for (const auto& entry : state["layouts"])
    {
      if (!entry.is_object() || !entry.contains("id") || !entry.contains("name"))
        continue;
      if (entry.contains("layout"))
        out["layouts"].push_back(LayoutProfile::catalogEntryFromProfile(entry));
      else
        out["layouts"].push_back(entry);
    }
  }
  return out;
}

void StateRepository::stripInlineProfiles(nlohmann::json& state) const
{
  if (!state.contains("layouts") || !state["layouts"].is_array())
    return;
  nlohmann::json catalog = nlohmann::json::array();
  for (const auto& entry : state["layouts"])
  {
    if (!entry.is_object() || !entry.contains("id"))
      continue;
    if (entry.contains("layout"))
      catalog.push_back(LayoutProfile::catalogEntryFromProfile(entry));
    else
      catalog.push_back(entry);
  }
  state["layouts"] = catalog;
}

void StateRepository::migrateStorage()
{
  nlohmann::json& s = app_.state;
  if (!s.is_object())
    s = nlohmann::json::object();

  const int version = s.contains("version") && s["version"].is_number_integer() ? s["version"].get<int>() : 1;

  if (!s.contains("layouts") || !s["layouts"].is_array() || s["layouts"].empty())
  {
    nlohmann::json layout = s.contains("layout") && s["layout"].is_object() ? s["layout"] : LayoutSchema::defaultLayoutObject();
    nlohmann::json lidars = s.contains("lidars") && s["lidars"].is_array() ? s["lidars"] : nlohmann::json::array();
    nlohmann::json profile = LayoutProfile::make("Mặc định", layout, lidars);
    LayoutSchema::ensure(profile["layout"]);
    saveProfileToDisk(profile);
    s["layouts"] = nlohmann::json::array({LayoutProfile::catalogEntryFromProfile(profile)});
    s["active_layout_id"] = profile["id"].get<std::string>();
  }
  else if (version < 3)
  {
    if (!s.contains("active_layout_id") || !s["active_layout_id"].is_string() ||
        !LayoutProfile::findIndex(s, s["active_layout_id"].get<std::string>()))
    {
      s["active_layout_id"] = s["layouts"][0]["id"].get<std::string>();
    }

    nlohmann::json catalog = nlohmann::json::array();
    for (auto& entry : s["layouts"])
    {
      if (!entry.is_object() || !entry.contains("id"))
        continue;
      nlohmann::json profile;
      if (entry.contains("layout"))
      {
        profile = entry;
        if (!profile.contains("name"))
          profile["name"] = "Layout";
        if (!profile.contains("lidars") || !profile["lidars"].is_array())
          profile["lidars"] = nlohmann::json::array();
        if (!profile.contains("created_at"))
          profile["created_at"] = IdUtil::nowIso8601();
        LayoutProfile::touch(profile);
        LayoutSchema::ensure(profile["layout"]);
        saveProfileToDisk(profile);
        catalog.push_back(LayoutProfile::catalogEntryFromProfile(profile));
      }
      else
      {
        const std::string id = entry["id"].get<std::string>();
        if (auto loaded = loadProfileFromDisk(id))
        {
          catalog.push_back(LayoutProfile::catalogEntryFromProfile(*loaded));
        }
        else
        {
          profile = LayoutProfile::make(entry["name"].get<std::string>(), LayoutSchema::defaultLayoutObject(), nlohmann::json::array());
          profile["id"] = id;
          profile["created_at"] = entry.value("created_at", IdUtil::nowIso8601());
          LayoutProfile::touch(profile);
          saveProfileToDisk(profile);
          catalog.push_back(LayoutProfile::catalogEntryFromProfile(profile));
        }
      }
    }
    s["layouts"] = catalog;
  }
  else
  {
    stripInlineProfiles(s);
    if (!s.contains("active_layout_id") || !s["active_layout_id"].is_string() ||
        !LayoutProfile::findIndex(s, s["active_layout_id"].get<std::string>()))
    {
      s["active_layout_id"] = s["layouts"][0]["id"].get<std::string>();
    }
  }

  s["version"] = 3;
  s.erase("layout");
  s.erase("lidars");
  loadActiveCache();
}

void StateRepository::bootstrapDefaultState()
{
  const nlohmann::json layout = LayoutSchema::defaultLayoutObject();
  nlohmann::json profile = LayoutProfile::make("Mặc định", layout, nlohmann::json::array());
  LayoutSchema::ensure(profile["layout"]);
  saveProfileToDisk(profile);
  app_.state = nlohmann::json{{"version", 3},
                   {"active_layout_id", profile["id"]},
                   {"layouts", nlohmann::json::array({LayoutProfile::catalogEntryFromProfile(profile)})}};
  app_.state["layout"] = profile["layout"];
  app_.state["lidars"] = profile["lidars"];
  app_.state["imus"] = profile.contains("imus") ? profile["imus"] : nlohmann::json::array();
}

StateRepository::StateRepository(std::filesystem::path data_path, Database& db) : db_(db)
{
  app_.data_path = std::move(data_path);
}

bool StateRepository::load()
{
  if (!db_.getDocument("state", app_.state))
  {
    bootstrapDefaultState();
    save();
    return true;
  }
  try
  {
    ensureSchema();
    save();
    return true;
  }
  catch (...)
  {
    bootstrapDefaultState();
    save();
    return false;
  }
}

void StateRepository::ensureSchema()
{
  migrateStorage();
}

bool StateRepository::saveProfile(const nlohmann::json& profile)
{
  return saveProfileToDisk(profile);
}

void StateRepository::reloadActiveCache()
{
  loadActiveCache();
}

bool StateRepository::deleteProfile(const std::string& id)
{
  return deleteProfileFile(id);
}

std::optional<nlohmann::json> StateRepository::loadProfileById(const std::string& id) const
{
  return loadProfileFromDisk(id);
}

bool StateRepository::saveAppState()
{
  if (!persistActiveProfile())
    return false;
  return save();
}

bool StateRepository::save() const
{
  try
  {
    const nlohmann::json disk = globalStateForDisk(app_.state);
    return db_.setDocument("state", disk);
  }
  catch (...)
  {
    return false;
  }
}

}  // namespace lm
