#include "server/api_server.hpp"

#include "mission/mission_enqueue.hpp"
#include "util/http_util.hpp"

namespace lm {

std::optional<nlohmann::json> ApiServer::enqueueMission(const nlohmann::json& request, std::string& err)
{
  nlohmann::json payload;
  if (!MissionEnqueue::buildPayload(mission_store_, request, payload, err))
    return std::nullopt;
  return mission_queue_.enqueue(payload, err);
}

bool ApiServer::enqueueRequest(const nlohmann::json& request, httplib::Response& res, int status_code)
{
  std::string err;
  const auto entry = enqueueMission(request, err);
  if (!entry)
  {
    HttpUtil::jsonError(res, 400, err);
    return false;
  }
  HttpUtil::addCors(res);
  res.status = status_code;
  res.set_header("Content-Type", "application/json; charset=utf-8");
  res.body = entry->dump();
  return true;
}

nlohmann::json ApiServer::toMirQueueEntry(const nlohmann::json& entry) const
{
  nlohmann::json out = nlohmann::json::object();
  if (entry.contains("id"))
    out["id"] = entry["id"];
  out["mission_id"] = entry.value("mission_id", std::string(""));
  out["state"] = entry.contains("status") ? entry["status"] : nlohmann::json("pending");
  out["message"] = entry.value("mission_name", std::string(""));
  if (entry.contains("priority") && entry["priority"].is_number())
    out["priority"] = entry["priority"];
  else
    out["priority"] = 0;
  out["robot_id"] = entry.value("robot_id", std::string("default"));
  return out;
}

void ApiServer::registerMissionRoutes(httplib::Server& svr)
{
  svr.Get("/api/missions", [this](const httplib::Request&, httplib::Response& res) {
    HttpUtil::addCors(res);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = mission_store_.snapshot().dump();
  });

  svr.Put("/api/missions", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    nlohmann::json payload;
    try
    {
      payload = nlohmann::json::parse(req.body);
    }
    catch (...)
    {
      return HttpUtil::jsonError(res, 400, "invalid JSON");
    }
    std::string err;
    if (!mission_store_.replace(payload, err))
      return HttpUtil::jsonError(res, 400, err);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = mission_store_.snapshot().dump();
  });
}

void ApiServer::registerIntegrationRoutes(httplib::Server& svr)
{
  svr.Get("/api/triggers", [this](const httplib::Request&, httplib::Response& res) {
    HttpUtil::addCors(res);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = mission_store_.listTriggers().dump();
  });

  svr.Post("/api/triggers", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    nlohmann::json payload;
    try
    {
      payload = nlohmann::json::parse(req.body);
    }
    catch (...)
    {
      return HttpUtil::jsonError(res, 400, "invalid JSON");
    }
    std::string err;
    const auto trigger = mission_store_.addTrigger(payload, err);
    if (!trigger)
      return HttpUtil::jsonError(res, 400, err);
    res.status = 201;
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = trigger->dump();
  });

  svr.Delete(R"(/api/triggers/([0-9a-fA-F]+))", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    std::string err;
    if (!mission_store_.deleteTrigger(req.matches[1].str(), err))
      return HttpUtil::jsonError(res, 400, err);
    res.status = 204;
  });

  svr.Get("/api/modbus/coils", [this](const httplib::Request&, httplib::Response& res) {
    HttpUtil::addCors(res);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = modbus_.coilStates().dump();
  });

  svr.Put(R"(/api/modbus/coils/([0-9]+))", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    nlohmann::json payload;
    try
    {
      payload = nlohmann::json::parse(req.body.empty() ? "{}" : req.body);
    }
    catch (...)
    {
      return HttpUtil::jsonError(res, 400, "invalid JSON");
    }
    const int coil_id = std::stoi(req.matches[1].str());
    const bool value = !payload.contains("value") || payload["value"].get<bool>();
    std::string err;
    if (!modbus_.writeCoil(coil_id, value, err))
      return HttpUtil::jsonError(res, 400, err);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = nlohmann::json({{"coil_id", coil_id}, {"value", value}}).dump();
  });

  svr.Post(R"(/api/modbus/coils/([0-9]+)/trigger)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const int coil_id = std::stoi(req.matches[1].str());
    std::string err;
    if (!modbus_.fireCoil(coil_id, err))
      return HttpUtil::jsonError(res, 400, err);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = nlohmann::json({{"ok", true}, {"coil_id", coil_id}}).dump();
  });

  svr.Get("/api/fleet/robots", [this](const httplib::Request&, httplib::Response& res) {
    HttpUtil::addCors(res);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = mission_store_.listRobots().dump();
  });

  svr.Get("/api/fleet/schedules", [this](const httplib::Request&, httplib::Response& res) {
    HttpUtil::addCors(res);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = mission_store_.listSchedules().dump();
  });

  svr.Post("/api/fleet/schedules", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    nlohmann::json payload;
    try
    {
      payload = nlohmann::json::parse(req.body);
    }
    catch (...)
    {
      return HttpUtil::jsonError(res, 400, "invalid JSON");
    }
    std::string err;
    const auto schedule = mission_store_.addSchedule(payload, err);
    if (!schedule)
      return HttpUtil::jsonError(res, 400, err);
    if (schedule->value("start_mode", "asap") == "asap" && schedule->value("enabled", true))
      scheduler_.runScheduleNow(schedule->value("id", ""), err);
    res.status = 201;
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = schedule->dump();
  });

  svr.Put(R"(/api/fleet/schedules/([0-9a-fA-F]+))", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    nlohmann::json payload;
    try
    {
      payload = nlohmann::json::parse(req.body);
    }
    catch (...)
    {
      return HttpUtil::jsonError(res, 400, "invalid JSON");
    }
    std::string err;
    if (!mission_store_.updateSchedule(req.matches[1].str(), payload, err))
      return HttpUtil::jsonError(res, 400, err);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = nlohmann::json({{"ok", true}}).dump();
  });

  svr.Delete(R"(/api/fleet/schedules/([0-9a-fA-F]+))", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    std::string err;
    if (!mission_store_.deleteSchedule(req.matches[1].str(), err))
      return HttpUtil::jsonError(res, 400, err);
    res.status = 204;
  });

  svr.Post(R"(/api/fleet/schedules/([0-9a-fA-F]+)/run)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    std::string err;
    if (!scheduler_.runScheduleNow(req.matches[1].str(), err))
      return HttpUtil::jsonError(res, 400, err);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = nlohmann::json({{"ok", true}}).dump();
  });
}

void ApiServer::registerMirV2Routes(httplib::Server& svr)
{
  svr.Get("/api/v2.0.0/missions", [this](const httplib::Request&, httplib::Response& res) {
    HttpUtil::addCors(res);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = mission_store_.listMissions().dump();
  });

  svr.Get("/api/v2.0.0/mission_queue", [this](const httplib::Request&, httplib::Response& res) {
    HttpUtil::addCors(res);
    nlohmann::json out = nlohmann::json::array();
    const nlohmann::json queue = mission_queue_.list();
    if (queue.is_array())
    {
      for (const auto& item : queue)
      {
        if (item.is_object())
          out.push_back(toMirQueueEntry(item));
      }
    }
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = out.dump();
  });

  svr.Post("/api/v2.0.0/mission_queue", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    nlohmann::json payload;
    try
    {
      payload = nlohmann::json::parse(req.body);
    }
    catch (...)
    {
      return HttpUtil::jsonError(res, 400, "invalid JSON");
    }
    if (!payload.contains("source"))
      payload["source"] = "rest_api_v2";
    std::string err;
    const auto entry = enqueueMission(payload, err);
    if (!entry)
      return HttpUtil::jsonError(res, 400, err);
    HttpUtil::addCors(res);
    res.status = 201;
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = toMirQueueEntry(*entry).dump();
  });

  svr.Delete("/api/v2.0.0/mission_queue", [this](const httplib::Request&, httplib::Response& res) {
    HttpUtil::addCors(res);
    std::string err;
    if (!mission_queue_.clearAll(err))
      return HttpUtil::jsonError(res, 400, err);
    res.status = 204;
  });

  svr.Get("/api/v2.0.0/status", [this](const httplib::Request&, httplib::Response& res) {
    HttpUtil::addCors(res);
    const auto runner = mission_queue_.runnerStatus();
    nlohmann::json body = {{"state_id", runner.value("state", "idle") == "running"   ? 3
                                              : runner.value("state", "") == "paused" ? 4
                                                                                      : 1},
                           {"state_text", runner.value("state", "idle")},
                           {"message", runner.value("message", "")}};
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = body.dump();
  });
}

}  // namespace lm
