#include "server/api_server.hpp"

#include "auth/auth_service.hpp"
#include "util/file_util.hpp"
#include "util/http_util.hpp"
#include "util/id_util.hpp"
#include "util/string_util.hpp"

#include <unordered_map>

namespace lm {

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

  svr.Post("/api/sites", [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");
    }
    std::string err;
    const auto created = site_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/sites/([^/]+)$)", [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 (!site_store_.update(id, body, err))
      return HttpUtil::jsonError(res, 404, err);
    const auto updated = site_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/sites/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    std::string err;
    if (!site_store_.remove(id, err))
      return HttpUtil::jsonError(res, 400, err);
    res.status = 204;
  });

  svr.Get(R"(/api/sites/([^/]+)/export$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string site_id = req.matches[1];
    const auto site = site_store_.find(site_id);
    if (!site)
      return HttpUtil::jsonError(res, 404, "site not found");

    nlohmann::json maps = nlohmann::json::array();
    for (const auto& map : map_store_.list())
    {
      if (map.value("site_id", "") == site_id)
        maps.push_back(map);
    }

    const nlohmann::json bundle = {{"format", "rbs-site-bundle"},
                                   {"version", 1},
                                   {"site", *site},
                                   {"maps", maps},
                                   {"transitions", transition_store_.list(site_id)},
                                   {"io_modules", io_module_store_.list(site_id)},
                                   {"paths", path_store_.list(site_id)},
                                   {"path_guides", path_guide_store_.list(site_id)},
                                   {"sounds", sound_store_.list()}};
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = bundle.dump(2);
  });

  svr.Post(R"(/api/sites/([^/]+)/import$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string site_id = req.matches[1];
    if (!site_store_.find(site_id))
      return HttpUtil::jsonError(res, 404, "site not found");

    nlohmann::json bundle;
    try
    {
      bundle = nlohmann::json::parse(req.body);
    }
    catch (...)
    {
      return HttpUtil::jsonError(res, 400, "invalid JSON");
    }
    if (bundle.value("format", "") != "rbs-site-bundle")
      return HttpUtil::jsonError(res, 400, "unsupported bundle format");

    nlohmann::json imported = {{"maps", nlohmann::json::array()},
                               {"io_modules", nlohmann::json::array()},
                               {"paths", nlohmann::json::array()},
                               {"path_guides", nlohmann::json::array()},
                               {"transitions", nlohmann::json::array()}};
    std::unordered_map<std::string, std::string> map_id_remap;

    if (bundle.contains("maps") && bundle["maps"].is_array())
    {
      for (const auto& map : bundle["maps"])
      {
        if (!map.is_object())
          continue;
        nlohmann::json payload = map;
        const std::string old_id = payload.value("id", "");
        payload["id"] = IdUtil::newId();
        payload["site_id"] = site_id;
        if (!old_id.empty())
          map_id_remap[old_id] = payload["id"].get<std::string>();
        std::string err;
        const auto created = map_store_.create(payload, err);
        if (!created)
          return HttpUtil::jsonError(res, 400, "map import failed: " + err);
        imported["maps"].push_back(*created);
      }
    }

    if (bundle.contains("io_modules") && bundle["io_modules"].is_array())
    {
      for (const auto& mod : bundle["io_modules"])
      {
        if (!mod.is_object())
          continue;
        nlohmann::json payload = mod;
        payload.erase("id");
        payload["site_id"] = site_id;
        if (const AuthSession* session = AuthService::activeSession())
        {
          if (!payload.contains("created_by") || payload["created_by"].get<std::string>().empty())
            payload["created_by"] = session->group_name.empty() ? session->username : session->group_name;
          if (!payload.contains("created_by_group") || payload["created_by_group"].get<std::string>().empty())
            payload["created_by_group"] = session->group_id;
        }
        std::string err;
        const auto created = io_module_store_.create(payload, err);
        if (!created)
          return HttpUtil::jsonError(res, 400, "io module import failed: " + err);
        imported["io_modules"].push_back(*created);
      }
    }

    if (bundle.contains("paths") && bundle["paths"].is_array())
    {
      for (const auto& path : bundle["paths"])
      {
        if (!path.is_object())
          continue;
        nlohmann::json payload = path;
        payload.erase("id");
        payload["site_id"] = site_id;
        const std::string old_map = payload.value("map_id", "");
        if (map_id_remap.count(old_map))
          payload["map_id"] = map_id_remap[old_map];
        std::string err;
        const auto saved = path_store_.upsert(payload, err);
        if (!saved)
          return HttpUtil::jsonError(res, 400, "path import failed: " + err);
        imported["paths"].push_back(*saved);
      }
    }

    if (bundle.contains("path_guides") && bundle["path_guides"].is_array())
    {
      for (const auto& guide : bundle["path_guides"])
      {
        if (!guide.is_object())
          continue;
        nlohmann::json payload = guide;
        payload.erase("id");
        payload["site_id"] = site_id;
        const std::string old_map = payload.value("map_id", "");
        if (map_id_remap.count(old_map))
          payload["map_id"] = map_id_remap[old_map];
        if (const AuthSession* session = AuthService::activeSession())
        {
          if (!payload.contains("created_by") || payload["created_by"].get<std::string>().empty())
            payload["created_by"] = session->group_name.empty() ? session->username : session->group_name;
          if (!payload.contains("created_by_group") || payload["created_by_group"].get<std::string>().empty())
            payload["created_by_group"] = session->group_id;
        }
        std::string err;
        const auto created = path_guide_store_.create(payload, err);
        if (!created)
          return HttpUtil::jsonError(res, 400, "path guide import failed: " + err);
        imported["path_guides"].push_back(*created);
      }
    }

    if (bundle.contains("transitions") && bundle["transitions"].is_array())
    {
      for (const auto& tr : bundle["transitions"])
      {
        if (!tr.is_object())
          continue;
        nlohmann::json payload = tr;
        payload.erase("id");
        payload["site_id"] = site_id;
        const std::string from_old = payload.value("from_map_id", "");
        const std::string to_old = payload.value("to_map_id", "");
        if (map_id_remap.count(from_old))
          payload["from_map_id"] = map_id_remap[from_old];
        if (map_id_remap.count(to_old))
          payload["to_map_id"] = map_id_remap[to_old];
        std::string err;
        const auto created = transition_store_.create(payload, err);
        if (!created)
          return HttpUtil::jsonError(res, 400, "transition import failed: " + err);
        imported["transitions"].push_back(*created);
      }
    }

    res.status = 201;
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = imported.dump(2);
  });

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

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

  svr.Post("/api/maps", [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())
    {
      body["created_by_user"] = session->user_id;
      body["created_by_group"] = session->group_id;
    }
    std::string err;
    const auto created = map_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/maps/([^/]+)$)", [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");
    }
    const auto existing = map_store_.find(id);
    std::string err;
    if (!map_store_.update(id, body, err))
      return HttpUtil::jsonError(res, 404, err);
    const auto updated = map_store_.find(id);
    if (existing && updated)
      path_service_.invalidateMapPositions(*existing, *updated);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = updated ? updated->dump() : nlohmann::json::object().dump();
  });

  svr.Delete(R"(/api/maps/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    const auto map = map_store_.find(id);
    if (!map)
      return HttpUtil::jsonError(res, 404, "map not found");

    if (const char* disabled = std::getenv("LM_AUTH_DISABLED"); !disabled || std::string(disabled) != "1")
    {
      const AuthSession* session = AuthService::activeSession();
      if (!session)
        return HttpUtil::jsonError(res, 401, "authentication required");
      if (!AuthService::canDeleteMap(*map, *session))
        return HttpUtil::jsonError(res, 403, "cannot delete map from another user group");
    }

    std::string err;
    if (!map_store_.remove(id, err))
      return HttpUtil::jsonError(res, 404, err);
    robot_runtime_.clearActiveMapIf(id);
    res.status = 204;
  });

  svr.Get(R"(/api/maps/([^/]+)/image$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    const auto path = map_store_.imagePath(id);
    if (!path)
      return HttpUtil::jsonError(res, 404, "map image not found");
    res.set_header("Content-Type", HttpUtil::contentTypeForPath(*path));
    res.body = FileUtil::readBinary(*path);
  });

  svr.Get(R"(/api/maps/([^/]+)/image/base$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    const auto path = map_store_.baseImagePath(id);
    if (!path)
      return HttpUtil::jsonError(res, 404, "map base image not found");
    res.set_header("Content-Type", HttpUtil::contentTypeForPath(*path));
    res.body = FileUtil::readBinary(*path);
  });

  svr.Post(R"(/api/maps/([^/]+)/image/composite$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    if (!req.form.has_file("file"))
      return HttpUtil::jsonError(res, 400, "file is required");
    const auto& file = req.form.get_file("file");
    std::string err;
    if (!map_store_.saveCompositeImageFile(id, file.content, err))
      return HttpUtil::jsonError(res, 400, err);
    const auto updated = map_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/maps/([^/]+)/image/base$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    if (!req.form.has_file("file"))
      return HttpUtil::jsonError(res, 400, "file is required");
    const auto& file = req.form.get_file("file");
    std::string err;
    if (!map_store_.saveBaseImageFile(id, file.content, err))
      return HttpUtil::jsonError(res, 400, err);
    const auto updated = map_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/maps/([^/]+)/image$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    if (!req.form.has_file("file"))
      return HttpUtil::jsonError(res, 400, "file is required");
    const auto& file = req.form.get_file("file");
    const std::string filename = file.filename.empty() ? "map.png" : file.filename;
    std::string err;
    if (!map_store_.saveImageFile(id, filename, file.content, err))
      return HttpUtil::jsonError(res, 400, err);
    const auto updated = map_store_.find(id);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = updated ? updated->dump() : nlohmann::json::object().dump();
  });

  svr.Get(R"(/api/maps/([^/]+)/yaml$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    const auto path = map_store_.yamlPath(id);
    if (!path)
      return HttpUtil::jsonError(res, 404, "map yaml not found");
    res.set_header("Content-Type", "text/yaml; charset=utf-8");
    res.body = FileUtil::readBinary(*path);
  });

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

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

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

  svr.Post("/api/sounds", [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");
    }
    std::string err;
    const auto created = sound_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/sounds/([^/]+)$)", [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 (!sound_store_.update(id, body, err))
      return HttpUtil::jsonError(res, 404, err);
    const auto updated = sound_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/sounds/([^/]+)$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    std::string err;
    if (!sound_store_.remove(id, err))
      return HttpUtil::jsonError(res, 404, err);
    res.status = 204;
  });

  svr.Get(R"(/api/sounds/([^/]+)/file$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    const auto path = sound_store_.filePath(id);
    if (!path)
      return HttpUtil::jsonError(res, 404, "sound file not found");
    res.set_header("Content-Type", HttpUtil::contentTypeForPath(*path));
    res.body = FileUtil::readBinary(*path);
  });

  svr.Post(R"(/api/sounds/([^/]+)/file$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    if (!req.form.has_file("file"))
      return HttpUtil::jsonError(res, 400, "file is required");
    const auto& file = req.form.get_file("file");
    const std::string filename = file.filename.empty() ? (id + ".wav") : file.filename;
    std::string err;
    if (!sound_store_.saveFile(id, filename, file.content, err))
      return HttpUtil::jsonError(res, 400, err);
    const auto updated = sound_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/sounds/([^/]+)/play$)", [this](const httplib::Request& req, httplib::Response& res) {
    HttpUtil::addCors(res);
    const std::string id = req.matches[1];
    const auto sound = sound_store_.find(id);
    if (!sound)
      return HttpUtil::jsonError(res, 404, "sound not found");
    if (sound->value("enabled", true) == false)
      return HttpUtil::jsonError(res, 400, "sound is disabled");

    int volume = sound->value("volume", 100);
    if (!req.body.empty())
    {
      try
      {
        const auto body = nlohmann::json::parse(req.body);
        if (body.contains("volume") && body["volume"].is_number_integer())
          volume = body["volume"].get<int>();
      }
      catch (...)
      {
        return HttpUtil::jsonError(res, 400, "invalid JSON");
      }
    }

    std::string err;
    if (!robot_runtime_.queueSoundPlay(id, volume, err))
      return HttpUtil::jsonError(res, 400, err);
    res.set_header("Content-Type", "application/json; charset=utf-8");
    res.body = nlohmann::json({{"ok", true}, {"sound_id", id}, {"volume", volume}}).dump();
  });

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

}  // namespace lm
