#ifndef BACKEND_CONTROLLERS_H
#define BACKEND_CONTROLLERS_H
#include <variant>
#include <unordered_map>
#include <shared_mutex>

#include <drogon/drogon.h>
#include <botan/rng.h>
#include <msd/channel.hpp>
#include <trantor/net/EventLoopThread.h>
#include <kubazip/zip/zip.h>

#include "db/db.h"

using req_type = const drogon::HttpRequestPtr&;
using cbk_type = std::function<void(const drogon::HttpResponsePtr &)>&&;

namespace api {
class admin : public drogon::HttpController<admin> {
public:
    METHOD_LIST_BEGIN
        METHOD_ADD(admin::users, "/users", drogon::Get, "Login", "Admin");
        METHOD_ADD(admin::set_role, "/set_role", drogon::Post, "Login", "Admin");
        METHOD_ADD(admin::logout, "/logout", drogon::Post, "Login", "Admin");
        METHOD_ADD(admin::delete_user, "/delete", drogon::Post, "Login", "Admin");
        METHOD_ADD(admin::disable_2fa, "/disable_2fa", drogon::Post, "Login", "Admin");
    METHOD_LIST_END

    void users(req_type, cbk_type);
    void set_role(req_type, cbk_type);
    void logout(req_type, cbk_type);
    void delete_user(req_type, cbk_type);
    void disable_2fa(req_type, cbk_type);
};

class auth : public drogon::HttpController<auth> {
public:
    METHOD_LIST_BEGIN
        METHOD_ADD(auth::gitlab, "/gitlab", drogon::Get);
        METHOD_ADD(auth::gitlab_callback, "/gitlab_callback?code={}", drogon::Get);
        METHOD_ADD(auth::signup, "/signup", drogon::Post);
        METHOD_ADD(auth::login, "/login", drogon::Post);
        METHOD_ADD(auth::refresh, "/refresh", drogon::Post, "Login");
        METHOD_ADD(auth::tfa_setup, "/2fa/setup", drogon::Post, "Login");
        METHOD_ADD(auth::tfa_complete, "/2fa/complete", drogon::Post, "Login");
        METHOD_ADD(auth::tfa_disable, "/2fa/disable", drogon::Post, "Login");
        METHOD_ADD(auth::change_password, "/change_password", drogon::Post, "Login");
        METHOD_ADD(auth::logout_all, "/logout_all", drogon::Post, "Login");
    METHOD_LIST_END

    struct gitlab_tokens {
        gitlab_tokens(std::string at, std::string rt) : at(std::move(at)), rt(std::move(rt)) {}
        std::string at, rt;
    };
    struct gitlab_user {
        gitlab_user(std::string name, bool isAdmin) : name(std::move(name)), is_admin(isAdmin) {}
        std::string name;
        bool is_admin;
    };

    static std::unique_ptr<Botan::RNG> rng;

    static std::optional<gitlab_tokens> get_gitlab_tokens(req_type, const std::string&, bool token);
    static std::optional<gitlab_user> get_gitlab_user(const std::string&);
    static bool verify2fa(const db::User&, uint32_t totp);
    static void send_mail(const db::User&);
    static std::string get_token(const db::User&);
    static void generate_root(db::User&);
    static void revoke_all(const db::User&);

    void gitlab(req_type, cbk_type);
    void gitlab_callback(req_type, cbk_type, std::string code);
    void signup(req_type, cbk_type);
    void login(req_type, cbk_type);
    void refresh(req_type, cbk_type);
    void tfa_setup(req_type, cbk_type);
    void tfa_complete(req_type, cbk_type);
    void tfa_disable(req_type, cbk_type);
    void change_password(req_type, cbk_type);
    void logout_all(req_type, cbk_type);
};

class fs : public drogon::HttpController<fs> {
public:
    METHOD_LIST_BEGIN
        METHOD_ADD(fs::root, "/root", drogon::Get, "Login");
        METHOD_ADD(fs::node, "/node/{}", drogon::Get, "Login");
        METHOD_ADD(fs::path, "/path/{}", drogon::Get, "Login");
        METHOD_ADD(fs::create_node_req<false>, "/createFolder", drogon::Post, "Login");
        METHOD_ADD(fs::create_node_req<true>, "/createFile", drogon::Post, "Login");
        METHOD_ADD(fs::delete_node_req, "/delete/{}", drogon::Post, "Login");
        METHOD_ADD(fs::upload, "/upload/{}", drogon::Post, "Login");
        METHOD_ADD(fs::create_zip, "/create_zip", drogon::Post, "Login");
        METHOD_ADD(fs::download, "/download", drogon::Post, "Login");
        METHOD_ADD(fs::download_multi, "/download_multi", drogon::Post, "Login");
        METHOD_ADD(fs::download_preview, "/download_preview/{}", drogon::Get, "Login");
        METHOD_ADD(fs::get_type, "/get_type/{}", drogon::Get, "Login");
    METHOD_LIST_END

    enum class create_node_error {
        INVALID_NAME,
        INVALID_PARENT,
        FILE_PARENT
    };

    struct mutex_stream {
        std::stringstream ss;
        std::mutex mutex;
        bool done = false;
    };

    static std::optional<db::INode> get_node(uint64_t node);
    static std::optional<db::INode> get_node_and_validate(const db::User& user, uint64_t node);
    static std::vector<db::INode> get_children(const db::INode& parent);
    static std::variant<db::INode, fs::create_node_error, std::tuple<bool, uint64_t>>
        create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &parent, bool force = false);
    static void delete_node(db::INode node, msd::channel<std::string>& chan, bool allow_root = false);
    static std::shared_ptr<std::shared_mutex> get_user_mutex(uint64_t user_id);

    void root(req_type, cbk_type);
    void node(req_type, cbk_type, uint64_t node);
    void path(req_type, cbk_type, uint64_t node);
    template<bool file> void create_node_req(req_type req, cbk_type cbk);
    void delete_node_req(req_type, cbk_type, uint64_t node);
    void upload(req_type, cbk_type, uint64_t node);
    void create_zip(req_type, cbk_type);
    void download(req_type, cbk_type);
    void download_multi(req_type, cbk_type);
    void download_preview(req_type, cbk_type, uint64_t node);
    void get_type(req_type, cbk_type, uint64_t node);

private:
    static trantor::EventLoop* get_zip_loop();
    static trantor::EventLoop* get_delete_loop();
    static void generate_path(db::INode node, std::string& str);
    static Json::Value generate_path(db::INode node);
    static uint64_t calc_total_size(const db::INode& base);
    static void add_to_zip(struct zip_t* zip, const std::string& key, const db::INode& node, const std::string& path);

    static uint64_t next_temp_id;
    static std::unordered_map<std::string, std::string> zip_to_temp_map;
    static std::unordered_map<std::string, std::tuple<std::string, uint64_t, uint64_t>> in_progress_zips;
};

class user : public drogon::HttpController<user> {
public:
    METHOD_LIST_BEGIN
        METHOD_ADD(user::info, "/info", drogon::Get, "Login");
        METHOD_ADD(user::delete_user, "/delete", drogon::Post, "Login");
    METHOD_LIST_END

    void info(req_type, cbk_type);
    void delete_user(req_type, cbk_type);
};
}
#endif //BACKEND_CONTROLLERS_H