#include "server/api_server.hpp"

#include "auth/auth_service.hpp"
#include "io/io_module_service.hpp"
#include "io/io_module_usage.hpp"
#include "util/http_util.hpp"
#include "util/string_util.hpp"

namespace lm {

void ApiServer::registerIoModuleRoutes(httplib::Server& svr)
{
  svr.Get("/api/io_modules", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string site_id = req.has_param("site_id") ? req.get_param_value("site_id") : "";
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = nlohmann::json({{"io_modules", io_module_store_.list(site_id)}}).dump();
  });

  svr.Post("/api/io_modules/test", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    nlohmann::json body;
    try
    {
      body = nlohmann::json::parse(req.body);
    }
    catch (...)
    {
      return HttpUtil::jsonError(res, 400, "invalid JSON");
    }
    const std::string ip = body.value("ip_address", "");
    int port = body.value("port", 502);
    if (body.contains("port") && body["port"].is_number())
      port = body["port"].get<int>();
    std::string err;
    const bool ok = io_module_service_.testConnection(ip, port, err);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    if (!ok)
      return HttpUtil::jsonError(res, 400, err);
    res.body = nlohmann::json({{"ok", true}}).dump();
  });

  svr.Get(R"(/api/io_modules/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    const auto mod = io_module_store_.find(id);
    if (!mod)
      return HttpUtil::jsonError(res, 404, "io module not found");
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = mod->dump();
  });

  svr.Get(R"(/api/io_modules/([^/]+)/runtime$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    if (!io_module_store_.find(id))
      return HttpUtil::jsonError(res, 404, "io module not found");
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = io_module_service_.runtimeSnapshot(id).dump();
  });

  svr.Post(R"(/api/io_modules/([^/]+)/output$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    const auto mod = io_module_store_.find(id);
    if (!mod)
      return HttpUtil::jsonError(res, 404, "io module not found");
    nlohmann::json body;
    try
    {
      body = nlohmann::json::parse(req.body);
    }
    catch (...)
    {
      return HttpUtil::jsonError(res, 400, "invalid JSON");
    }
    const std::string module_type = mod->value("module_type", "bluetooth");
    int port = body.value("port", body.value("output", IoModuleService::defaultPortForType(module_type)));
    if (body.contains("output") && body["output"].is_number())
      port = body["output"].get<int>();
    else if (body.contains("port") && body["port"].is_number())
      port = body["port"].get<int>();
    bool on = true;
    if (body.contains("value"))
    {
      if (body["value"].is_boolean())
        on = body["value"].get<bool>();
      else if (body["value"].is_number())
        on = body["value"].get<int>() != 0;
    }
    const int timeout_ms = body.value("timeout_ms", 0);
    std::string err;
    bool ok = false;
    if (timeout_ms > 0 && on)
      ok = io_module_service_.setOutputTimed(id, port, on, timeout_ms, err);
    else
      ok = io_module_service_.setOutput(id, port, on, err);
    if (!ok)
      return HttpUtil::jsonError(res, 400, err);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = io_module_service_.runtimeSnapshot(id).dump();
  });

  svr.Post("/api/io_modules", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    nlohmann::json body;
    try
    {
      body = nlohmann::json::parse(req.body);
    }
    catch (...)
    {
      return HttpUtil::jsonError(res, 400, "invalid JSON");
    }
    if (const AuthSession* session = AuthService::activeSession())
    {
      if (!body.contains("created_by") || !body["created_by"].is_string() ||
          body["created_by"].get<std::string>().empty())
      {
        body["created_by"] = session->group_name.empty() ? session->username : session->group_name;
      }
      if (!body.contains("created_by_group") || !body["created_by_group"].is_string() ||
          body["created_by_group"].get<std::string>().empty())
        body["created_by_group"] = session->group_id;
    }
    std::string err;
    const auto created = io_module_store_.create(body, err);
    if (!created)
      return HttpUtil::jsonError(res, 400, err);
    res.status = 201;
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = created->dump();
  });

  svr.Put(R"(/api/io_modules/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    nlohmann::json body;
    try
    {
      body = nlohmann::json::parse(req.body);
    }
    catch (...)
    {
      return HttpUtil::jsonError(res, 400, "invalid JSON");
    }
    std::string err;
    if (!io_module_store_.update(id, body, err))
      return HttpUtil::jsonError(res, 400, err);
    const auto updated = io_module_store_.find(id);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = updated ? updated->dump() : nlohmann::json::object().dump();
  });

  svr.Delete(R"(/api/io_modules/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    const auto existing = io_module_store_.find(id);
    if (!existing)
      return HttpUtil::jsonError(res, 404, "io module not found");
    if (const AuthSession* session = AuthService::activeSession())
    {
      if (!AuthService::canDeleteMap(*existing, *session))
        return HttpUtil::jsonError(res, 403, "cannot delete io module from another user group");
    }
    const auto usages =
        findIoModuleUsages(id, existing->value("name", ""), map_store_, mission_store_);
    if (usages.value("in_use", false))
    {
      res.status = 409;
      res.set_header("Content-Type", "application/json; charset=utf-8");
      res.body = nlohmann::json({{"error", "io module is in use"}, {"usages", usages}}).dump();
      return;
    }
    std::string err;
    if (!io_module_store_.remove(id, err))
      return HttpUtil::jsonError(res, 400, err);
    res.status = 204;
  });

  svr.Post(R"(/api/io_modules/([^/]+)/connect$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    std::string err;
    if (!io_module_service_.connect(id, err))
      return HttpUtil::jsonError(res, 400, err);
    const auto updated = io_module_store_.find(id);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = updated ? updated->dump() : nlohmann::json::object().dump();
  });

  svr.Post(R"(/api/io_modules/([^/]+)/disconnect$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    std::string err;
    if (!io_module_service_.disconnect(id, err))
      return HttpUtil::jsonError(res, 400, err);
    const auto updated = io_module_store_.find(id);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = updated ? updated->dump() : nlohmann::json::object().dump();
  });
}

}  // namespace lm
