#include "mission/position_resolver.hpp"

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

namespace lm {

namespace {

std::string lowerCopy(const std::string& s)
{
  return StringUtil::toLower(StringUtil::trimCopy(s));
}

}  // namespace

PositionResolver::PositionResolver(MapStore& maps) : maps_(maps) {}

nlohmann::json PositionResolver::listPositions() const
{
  nlohmann::json out = nlohmann::json::array();
  const auto maps = maps_.list();
  if (!maps.is_array())
    return out;

  for (const auto& map : maps)
  {
    if (!map.is_object())
      continue;
    const std::string map_id = map.value("id", "");
    const std::string map_name = map.value("name", map_id);
    const auto& zones = map.value("zones", nlohmann::json::array());
    if (!zones.is_array())
      continue;
    for (const auto& z : zones)
    {
      if (!z.is_object() || z.value("type", "") != "position")
        continue;
      const std::string pid = z.value("id", "");
      if (pid.empty())
        continue;
      const std::string pname = z.value("name", pid);
      out.push_back({{"map_id", map_id},
                     {"map_name", map_name},
                     {"position_id", pid},
                     {"name", pname},
                     {"label", map_name + " / " + pname}});
    }
  }
  return out;
}

std::optional<ResolvedPosition> PositionResolver::resolve(const std::string& ref,
                                                          const std::string& prefer_map_id) const
{
  const std::string needle = StringUtil::trimCopy(ref);
  if (needle.empty())
    return std::nullopt;

  const auto maps = maps_.list();
  if (!maps.is_array())
    return std::nullopt;

  auto tryMatch = [&](const nlohmann::json& map, bool by_id) -> std::optional<ResolvedPosition> {
    if (!map.is_object())
      return std::nullopt;
    const std::string map_id = map.value("id", "");
    const std::string map_name = map.value("name", map_id);
    const auto& zones = map.value("zones", nlohmann::json::array());
    if (!zones.is_array())
      return std::nullopt;
    const std::string ref_lower = lowerCopy(needle);
    for (const auto& z : zones)
    {
      if (!z.is_object() || z.value("type", "") != "position")
        continue;
      const std::string pid = z.value("id", "");
      const std::string pname = z.value("name", "");
      if (by_id)
      {
        if (pid != needle)
          continue;
      }
      else if (lowerCopy(pname) != ref_lower)
      {
        continue;
      }
      ResolvedPosition out;
      out.map_id = map_id;
      out.position_id = pid;
      out.name = pname;
      out.label = map_name + " / " + (pname.empty() ? pid : pname);
      return out;
    }
    return std::nullopt;
  };

  if (!prefer_map_id.empty())
  {
    for (const auto& map : maps)
    {
      if (map.value("id", "") != prefer_map_id)
        continue;
      if (auto hit = tryMatch(map, true))
        return hit;
      if (auto hit = tryMatch(map, false))
        return hit;
    }
  }

  for (const auto& map : maps)
  {
    if (auto hit = tryMatch(map, true))
      return hit;
  }
  for (const auto& map : maps)
  {
    if (auto hit = tryMatch(map, false))
      return hit;
  }
  return std::nullopt;
}

std::string getActiveMapIdFromDb(Database& db)
{
  nlohmann::json rt;
  if (!db.getDocument("robot_runtime", rt) || !rt.is_object())
    return "";
  if (!rt.contains("active_map_id") || rt["active_map_id"].is_null())
    return "";
  return rt.value("active_map_id", "");
}

bool setActiveMapOnRobotRuntimeDoc(Database& db, const std::string& map_id, std::string& err)
{
  if (map_id.empty())
  {
    err = "map_id is required";
    return false;
  }
  nlohmann::json rt;
  if (!db.getDocument("robot_runtime", rt) || !rt.is_object())
    rt = nlohmann::json::object();
  rt["active_map_id"] = map_id;
  rt["updated_at"] = IdUtil::nowIso8601();
  if (!db.setDocument("robot_runtime", rt))
  {
    err = "failed to update robot_runtime";
    return false;
  }
  return true;
}

}  // namespace lm
