#pragma once

#include <httplib.h>
#include <nlohmann/json.hpp>

#include <filesystem>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>

namespace lm {

class Database;

struct AuthSession
{
  std::string token;
  std::string user_id;
  std::string username;
  std::string group_id;
  std::string group_name;
  nlohmann::json permissions;
};

class AuthService
{
public:
  explicit AuthService(Database& db);

  httplib::Server::HandlerResponse preRoute(const httplib::Request& req, httplib::Response& res);

  static const AuthSession* activeSession() { return tls_session_; }
  static bool canDeleteMap(const nlohmann::json& map, const AuthSession& session);

  std::optional<nlohmann::json> loginPassword(const std::string& username,
                                              const std::string& password,
                                              std::string& err);
  std::optional<nlohmann::json> loginPin(const std::string& pin, std::string& err);
  bool logout(const std::string& token);
  std::optional<nlohmann::json> sessionInfo(const std::string& token) const;
  bool changePassword(const std::string& token,
                      const std::string& current_password,
                      const std::string& new_password,
                      std::string& err);
  std::optional<nlohmann::json> changeProfile(const std::string& token,
                                              const nlohmann::json& payload,
                                              std::string& err);

  nlohmann::json listGroups() const;
  std::optional<nlohmann::json> createGroup(const nlohmann::json& payload, std::string& err);
  std::optional<nlohmann::json> updateGroup(const std::string& id,
                                              const nlohmann::json& payload,
                                              std::string& err);
  bool deleteGroup(const std::string& id, std::string& err);
  nlohmann::json listUsers() const;
  std::optional<nlohmann::json> createUser(const nlohmann::json& payload, std::string& err);
  std::optional<nlohmann::json> updateUser(const std::string& id,
                                           const nlohmann::json& payload,
                                           std::string& err);
  bool deleteUser(const std::string& id, std::string& err);

  void registerRoutes(httplib::Server& svr);

private:
  Database& db_;
  mutable std::mutex mu_;
  nlohmann::json data_;
  std::unordered_map<std::string, AuthSession> sessions_;
  thread_local static const AuthSession* tls_session_;

  void loadOrSeed();
  void saveUnlocked();
  std::string extractToken(const httplib::Request& req) const;
  std::optional<AuthSession> buildSessionUnlocked(const nlohmann::json& user);
  bool permissionAllows(const nlohmann::json& perms, const std::string& resource, bool write) const;
  bool authorizeApiRequest(const httplib::Request& req, httplib::Response& res);
  static bool isPublicApiPath(const std::string& path, const std::string& method);
  static std::optional<std::string> resourceForApiPath(const std::string& path);
  static bool requiresWrite(const std::string& method);
  static nlohmann::json userPublicView(const nlohmann::json& user, const nlohmann::json& group);
  const nlohmann::json* findUserByIdUnlocked(const std::string& id) const;
  const nlohmann::json* findUserByUsernameUnlocked(const std::string& username) const;
  const nlohmann::json* findGroupByIdUnlocked(const std::string& id) const;
  bool verifyPasswordUnlocked(const nlohmann::json& user, const std::string& password) const;
  bool verifyPinUnlocked(const nlohmann::json& user, const std::string& pin) const;
  bool groupAllowsPinUnlocked(const std::string& group_id) const;
  bool pinInUseUnlocked(const std::string& pin, const std::string& except_user_id = "") const;
  static bool isBuiltinGroupId(const std::string& id);
  static nlohmann::json normalizePermissions(const nlohmann::json& perms);
  static nlohmann::json groupPublicView(const nlohmann::json& group, size_t user_count);
  bool canManageGroupUnlocked(const std::string& editor_group_id, const std::string& target_group_id) const;
  size_t countUsersInGroupUnlocked(const std::string& group_id) const;
  const nlohmann::json* findGroupByNameUnlocked(const std::string& name, const std::string& except_id = "") const;
};

}  // namespace lm
