#include "mission/mission_store.hpp"

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

namespace lm {

namespace {

constexpr int kMinCoilId = 1001;
constexpr int kMaxCoilId = 2000;

bool coilIdValid(int coil_id)
{
  return coil_id >= kMinCoilId && coil_id <= kMaxCoilId;
}

}  // namespace

MissionStore::MissionStore(Database& db) : db_(db)
{
  load();
}

void MissionStore::load()
{
  std::lock_guard<std::mutex> lock(mu_);
  data_ = nlohmann::json::object();
  const bool existed = db_.getDocument("missions", data_);
  ensureSchemaUnlocked();
  if (!existed)
    saveUnlocked();
}

void MissionStore::ensureSchemaUnlocked()
{
  if (!data_.contains("version"))
    data_["version"] = 1;
  if (!data_.contains("missions") || !data_["missions"].is_array())
    data_["missions"] = nlohmann::json::array();
  if (!data_.contains("triggers") || !data_["triggers"].is_array())
    data_["triggers"] = nlohmann::json::array();
  if (!data_.contains("schedules") || !data_["schedules"].is_array())
    data_["schedules"] = nlohmann::json::array();
  if (!data_.contains("groups") || !data_["groups"].is_array())
    data_["groups"] = nlohmann::json::array({"Missions", "Move", "Logic", "I/O", "Cart", "Misc"});
  if (!data_.contains("robots") || !data_["robots"].is_array())
  {
    data_["robots"] = nlohmann::json::array({{{"id", "default"},
                                              {"name", "Robot chính"},
                                              {"serial", "PX-001"},
                                              {"online", true}}});
  }
  if (!data_.contains("dashboard") || !data_["dashboard"].is_object())
    data_["dashboard"] = nlohmann::json::object({{"widgets", nlohmann::json::array()}});
}

void MissionStore::saveUnlocked() const
{
  db_.setDocument("missions", data_);
}

nlohmann::json MissionStore::snapshot() const
{
  std::lock_guard<std::mutex> lock(mu_);
  return data_;
}

bool MissionStore::replace(const nlohmann::json& payload, std::string& err)
{
  if (!payload.is_object())
  {
    err = "payload must be an object";
    return false;
  }
  std::lock_guard<std::mutex> lock(mu_);
  if (payload.contains("missions") && payload["missions"].is_array())
    data_["missions"] = payload["missions"];
  if (payload.contains("groups") && payload["groups"].is_array())
    data_["groups"] = payload["groups"];
  if (payload.contains("triggers") && payload["triggers"].is_array())
    data_["triggers"] = payload["triggers"];
  if (payload.contains("schedules") && payload["schedules"].is_array())
    data_["schedules"] = payload["schedules"];
  ensureSchemaUnlocked();
  saveUnlocked();
  return true;
}

nlohmann::json MissionStore::listMissions() const
{
  std::lock_guard<std::mutex> lock(mu_);
  return data_["missions"];
}

std::optional<nlohmann::json> MissionStore::findMission(const std::string& id) const
{
  std::lock_guard<std::mutex> lock(mu_);
  if (!data_.contains("missions") || !data_["missions"].is_array())
    return std::nullopt;
  for (const auto& m : data_["missions"])
  {
    if (m.is_object() && m.value("id", "") == id)
      return m;
  }
  return std::nullopt;
}

nlohmann::json MissionStore::listTriggers() const
{
  std::lock_guard<std::mutex> lock(mu_);
  return data_["triggers"];
}

std::optional<nlohmann::json> MissionStore::addTrigger(const nlohmann::json& payload, std::string& err)
{
  if (!payload.is_object())
  {
    err = "payload must be an object";
    return std::nullopt;
  }
  if (!payload.contains("name") || !payload["name"].is_string())
  {
    err = "name is required";
    return std::nullopt;
  }
  if (!payload.contains("coil_id") || !payload["coil_id"].is_number_integer())
  {
    err = "coil_id is required";
    return std::nullopt;
  }
  if (!payload.contains("mission_id") || !payload["mission_id"].is_string())
  {
    err = "mission_id is required";
    return std::nullopt;
  }
  const int coil_id = payload["coil_id"].get<int>();
  if (!coilIdValid(coil_id))
  {
    err = "coil_id must be between 1001 and 2000";
    return std::nullopt;
  }
  const std::string mission_id = payload["mission_id"].get<std::string>();
  if (!findMission(mission_id))
  {
    err = "mission not found";
    return std::nullopt;
  }

  std::lock_guard<std::mutex> lock(mu_);
  for (const auto& t : data_["triggers"])
  {
    if (t.is_object() && t.value("coil_id", 0) == coil_id)
    {
      err = "coil_id already assigned";
      return std::nullopt;
    }
  }

  nlohmann::json trigger = {{"id", IdUtil::newId()},
                            {"name", StringUtil::trimCopy(payload["name"].get<std::string>())},
                            {"coil_id", coil_id},
                            {"mission_id", mission_id},
                            {"enabled", !payload.contains("enabled") || payload["enabled"].get<bool>()},
                            {"created_at", IdUtil::nowIso8601()}};
  data_["triggers"].push_back(trigger);
  saveUnlocked();
  return trigger;
}

bool MissionStore::deleteTrigger(const std::string& id, std::string& err)
{
  std::lock_guard<std::mutex> lock(mu_);
  if (!data_["triggers"].is_array())
  {
    err = "triggers unavailable";
    return false;
  }
  const auto before = data_["triggers"].size();
  nlohmann::json next = nlohmann::json::array();
  for (const auto& t : data_["triggers"])
  {
    if (t.is_object() && t.value("id", "") == id)
      continue;
    next.push_back(t);
  }
  if (next.size() == before)
  {
    err = "trigger not found";
    return false;
  }
  data_["triggers"] = std::move(next);
  saveUnlocked();
  return true;
}

std::optional<nlohmann::json> MissionStore::findTriggerByCoil(int coil_id) const
{
  std::lock_guard<std::mutex> lock(mu_);
  if (!data_["triggers"].is_array())
    return std::nullopt;
  for (const auto& t : data_["triggers"])
  {
    if (!t.is_object())
      continue;
    if (t.value("coil_id", 0) == coil_id && t.value("enabled", true))
      return t;
  }
  return std::nullopt;
}

nlohmann::json MissionStore::listSchedules() const
{
  std::lock_guard<std::mutex> lock(mu_);
  return data_["schedules"];
}

std::optional<nlohmann::json> MissionStore::addSchedule(const nlohmann::json& payload, std::string& err)
{
  if (!payload.is_object())
  {
    err = "payload must be an object";
    return std::nullopt;
  }
  if (!payload.contains("name") || !payload["name"].is_string())
  {
    err = "name is required";
    return std::nullopt;
  }
  if (!payload.contains("mission_id") || !payload["mission_id"].is_string())
  {
    err = "mission_id is required";
    return std::nullopt;
  }
  const std::string mission_id = payload["mission_id"].get<std::string>();
  if (!findMission(mission_id))
  {
    err = "mission not found";
    return std::nullopt;
  }

  const std::string start_mode =
      payload.contains("start_mode") && payload["start_mode"].is_string() ? payload["start_mode"].get<std::string>()
                                                                          : "asap";
  if (start_mode != "asap" && start_mode != "scheduled")
  {
    err = "start_mode must be asap or scheduled";
    return std::nullopt;
  }

  nlohmann::json schedule = {{"id", IdUtil::newId()},
                             {"name", StringUtil::trimCopy(payload["name"].get<std::string>())},
                             {"mission_id", mission_id},
                             {"robot_id", payload.value("robot_id", "default")},
                             {"priority", payload.contains("priority") && payload["priority"].is_number()
                                              ? payload["priority"].get<int>()
                                              : 0},
                             {"start_mode", start_mode},
                             {"start_at", payload.contains("start_at") ? payload["start_at"] : nullptr},
                             {"enabled", !payload.contains("enabled") || payload["enabled"].get<bool>()},
                             {"last_queued_at", nullptr},
                             {"created_at", IdUtil::nowIso8601()}};
  std::lock_guard<std::mutex> lock(mu_);
  data_["schedules"].push_back(schedule);
  saveUnlocked();
  return schedule;
}

bool MissionStore::updateSchedule(const std::string& id, const nlohmann::json& payload, std::string& err)
{
  std::lock_guard<std::mutex> lock(mu_);
  if (!data_["schedules"].is_array())
  {
    err = "schedules unavailable";
    return false;
  }
  for (auto& s : data_["schedules"])
  {
    if (!s.is_object() || s.value("id", "") != id)
      continue;
    if (payload.contains("enabled") && payload["enabled"].is_boolean())
      s["enabled"] = payload["enabled"];
    if (payload.contains("priority") && payload["priority"].is_number())
      s["priority"] = payload["priority"];
    if (payload.contains("start_at"))
      s["start_at"] = payload["start_at"];
    if (payload.contains("start_mode") && payload["start_mode"].is_string())
      s["start_mode"] = payload["start_mode"];
    saveUnlocked();
    return true;
  }
  err = "schedule not found";
  return false;
}

bool MissionStore::deleteSchedule(const std::string& id, std::string& err)
{
  std::lock_guard<std::mutex> lock(mu_);
  const auto before = data_["schedules"].size();
  nlohmann::json next = nlohmann::json::array();
  for (const auto& s : data_["schedules"])
  {
    if (s.is_object() && s.value("id", "") == id)
      continue;
    next.push_back(s);
  }
  if (next.size() == before)
  {
    err = "schedule not found";
    return false;
  }
  data_["schedules"] = std::move(next);
  saveUnlocked();
  return true;
}

std::optional<nlohmann::json> MissionStore::findSchedule(const std::string& id) const
{
  std::lock_guard<std::mutex> lock(mu_);
  for (const auto& s : data_["schedules"])
  {
    if (s.is_object() && s.value("id", "") == id)
      return s;
  }
  return std::nullopt;
}

nlohmann::json MissionStore::listRobots() const
{
  std::lock_guard<std::mutex> lock(mu_);
  return data_["robots"];
}

}  // namespace lm
