/*******************************************************************************
 * Copyright (c) 2025, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

#include <algorithm>
#include <iostream>
#include <cstring>
#include <mutex>
#include <shared_mutex>
#include <fstream>
#include <thread>
#include <map>
#include <memory>

#include <cmdplus/cmdplus.h>
#include <confplus/conf.h>

#include <netplus/socket.h>

#include <httppp/httpd.h>
#include <htmlpp/html.h>
#include <htmlpp/exception.h>

#include "backend.h"
#include "authdb.h"
#include "user.h"
#include "domain.h"
#include "group.h"
#include "types.h"
#include "hash/sha512.h"
#include "admin.h"
#include "gpo.h"
#include "client.h"
#include "session.h"
#include "api.h"
#include "cluster.h"
#include "gpo_default.h"
#include "plugin.h"
#include "log.h"

#include "favicon.h"

#include <json-c/json.h>
#include <netplus/exception.h>

#ifndef _WIN32
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif

namespace authdb {

    AdPlugin       *g_AdPlugin       = nullptr;
    PluginManager  *g_PluginManager   = nullptr;

    class Client   AuthClient;
    Session        *AuthSession = nullptr;

    std::mutex g_wizzard;

    bool initalized=true;
    bool DEBUG=false;



    class AuthDB : public libhttppp::HttpEvent{
    public:
        AuthDB(authdb::AuthBackend &backend,std::vector<netplus::socket*> ssock) :
            HttpEvent(ssock),
            _AdminBackend(backend),
            _AdminInterface(backend,ssock){
            std::ifstream authfs(GETDATA(authdb.js), std::ios::ate);
            size_t asize = authfs.tellg();
            _AuthJs.resize(asize);
            authfs.seekg(0);
            authfs.read(_AuthJs.data(),asize);

            std::ifstream cssfs(GETDATA(style.css), std::ios::ate);
            size_t csssize = cssfs.tellg();
            _AuthCss.resize(csssize);
            cssfs.seekg(0);
            cssfs.read(_AuthCss.data(),csssize);

            _AdminInterface.setDomainResolver([this](const char *cdid) -> AuthBackend* {
                uuid::uuid did;
                if (did.parse(cdid) != 0)
                    return nullptr;
                return getDomainBackend(did);
            });
        };

        void WizzardEvent(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args){
            std::lock_guard<std::mutex> wlock(g_wizzard);

            libhttppp::HttpForm form;

            form.parse(curreq);

            std::string username,password,pwhash;
            
            for(const auto &curformdat : form.urlData()){
                if(curformdat.key=="username")
                    username=curformdat.value;
                else if(curformdat.key=="password")
                    password=curformdat.value;
                }

                if(!password.empty() && !username.empty()){
                    sha512 hash;
                    hash.hash(password,pwhash);

                    authdb::User user;

                    ::uuid::uuid adminid,admingid;

                    adminid.generate();

                    admingid.generate();

                    class authdb::UserData udat(adminid);

                    udat.setUserName(username.c_str());

                    authdb::sha512 sha;
                    sha.hash(password,pwhash);

                    udat.setPwHash(pwhash.c_str());

                    user.create(_AdminBackend,&udat);

                    authdb::Group group;

                    class authdb::GroupData gdat(admingid);

                    gdat.setName("SuperAdmin");

                    gdat.addMember(adminid);

                    group.create(_AdminBackend,&gdat);

                    class Gpo gpo;
                    std::vector<::uuid::uuid> gallow;
                    gallow.emplace_back(admingid);
                    size_t i=0;
                    try {
                        while(gpo_default[i][0]){
                            GpoData gpodat(gpo_default[i][0]);
                            gpodat.setName(gpo_default[i][1]);
                            gpodat.setDesc(gpo_default[i][2]);
                            if(strcmp(gpo_default[i][3],"true")==0)
                                gpodat.setGpoValue(true);
                            else
                                gpodat.setGpoValue(false);
                            gpodat.setAllowed(gallow);
                            gpo.create(_AdminBackend,&gpodat);
                            ++i;
                        }

                    }catch(std::exception &e){
                        std::cerr << e.what() << std::endl;
                    }

                    libhttppp::HTTPException exp;
                    exp[999] << "wizzard finished";
                    throw exp;
                }

                libhtmlpp::HtmlElement index;

                libhtmlpp::HtmlPage page;
                try {
                    page.loadFile(index,GETDATA(wizzard.html));
                } catch (libhtmlpp::HTMLException &e) {
                    libhttppp::HttpResponse erep;
                    erep.setState(HTTP500);
                    erep.setContentType("text/html");
                    erep.send(curreq, e.what(), strlen(e.what()));
                    return;
                }

                libhttppp::HttpResponse rep;

                libhtmlpp::HtmlString sendat;

                libhtmlpp::print(*static_cast<libhtmlpp::Element*>(&index),sendat,false);

                rep.setState(HTTP200);
                rep.setContentType("text/html");
                rep.send(curreq,sendat.data(),sendat.length());

        }

        void RequestEvent(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args){
            const char *methods[]={"PARSE","GET","POST","DELETE","PUT"};
            int rtype=curreq.getRequestType();
            const char *method=(rtype>=0 && rtype<=4) ? methods[rtype] : "UNKNOWN";

            std::string clientAddr;
            if(!curreq.slots.empty() && curreq.slots[0].csock)
                curreq.slots[0].csock->getAddress(clientAddr);

            try {
                if(curreq.getRequestURL().compare(0,10,"/authdb.js")==0){
                    libhttppp::HttpResponse rep;
                    rep.setState(HTTP200);
                    rep.setContentType("text/javascript");
                    rep.send(curreq,_AuthJs,_AuthJs.size());
                    return;
                }else if(curreq.getRequestURL().compare(0,10,"/style.css")==0){
                    libhttppp::HttpResponse rep;
                    rep.setState(HTTP200);
                    rep.setContentType("text/css");
                    rep.send(curreq,_AuthCss,_AuthCss.size());
                    return;
                }else if(curreq.getRequestURL().compare(0,12,"/favicon.ico")==0){
                    libhttppp::HttpResponse rep;
                    rep.setState(HTTP200);
                    rep.setContentType("image/ico");
                    std::vector<char> out;
                    std::copy(FAVICON,FAVICON+FAVICON_LEN,std::back_inserter(out));
                    rep.send(curreq,out,FAVICON_LEN);
                    return;
                }

                if(_AdminBackend.end()<=sizeof(authdb::AuthHeader)){
                    // API requests should get JSON error, not the wizard HTML
                    if(curreq.getRequestURL().compare(0,4,"/api",4)==0){
                        libhttppp::HttpResponse rep;
                        rep.setState(HTTP503);
                        rep.setContentType("application/json");
                        rep.send(curreq,"[{\"error\":\"not initialized\"}]",29);
                        return;
                    }
                    // Cluster has data but not enough peers to reconstruct — don't
                    // show the wizard (would create conflicting admin data).
                    if(_AdminBackend.clusterDataExists()){
                        libhttppp::HttpResponse rep;
                        rep.setState(HTTP503);
                        rep.setContentType("text/html");
                        rep.send(curreq,"cluster data unavailable — waiting for peers",45);
                        return;
                    }
                    std::cerr << "[AUTHDB] wizard check: end=" << _AdminBackend.end()
                              << " header=" << sizeof(authdb::AuthHeader) << std::endl;
                    AuthBackend::Guard admlock(_AdminBackend);
                    try{
                        WizzardEvent(curreq,tid,args);
                        return;
                    }catch(libhttppp::HTTPException &e){
                        if(e.getErrorType()!=999){
                            libhttppp::HttpResponse rep;
                            rep.setState(HTTP500);
                            rep.setContentType("text/html");
                            rep.send(curreq,e.what(),e.what().length());
                            return;
                        }
                        std::cerr << "[AUTHDB] wizard done, end=" << _AdminBackend.end() << std::endl;
                    }
                }

                if(curreq.getRequestURL().compare(0,9,"/settings",9)==0){
                    try{
                        _AdminInterface.Request(curreq,tid,args);
                        g_Log.access(clientAddr,method,curreq.getRequestURL(),200);
                    }catch(AuthBackendError &e){
                        g_Log.error(clientAddr,e.what());
                        g_Log.access(clientAddr,method,curreq.getRequestURL(),500);
                        libhttppp::HttpResponse rep;
                        rep.setState(HTTP500);
                        rep.setContentType("text/html");
                        rep.send(curreq,e.what(),strlen(e.what()));
                    }
                }else if(curreq.getRequestURL().compare(0,4,"/api",4)==0){
                    char domain[255]={0};

                    if(sscanf(curreq.getRequestURL().c_str(),"/api/%254[^/]s",domain)<0){
                        libhttppp::HttpResponse rep;
                        rep.setState(HTTP500);
                        rep.setContentType("text/html");
                        rep.send(curreq,"can't parse url",15);
                        return;
                    }

                    ::uuid::uuid did;

                    if(did.parse(domain)!=0){
                        libhttppp::HttpResponse rep;
                        rep.setState(HTTP500);
                        rep.setContentType("application/json");
                        rep.send(curreq,"[{\"error\":\"can't parse domain id\"}]",36);
                        return;
                    }

                    AuthBackend *apidb = nullptr;
                    try {
                        apidb = getDomainBackend(did);
                    }catch(std::exception &e){
                        g_Log.error(clientAddr,e.what());
                        json_object *jerr = json_object_new_array();
                        json_object *eobj = json_object_new_object();
                        json_object_object_add(eobj,"error",json_object_new_string(e.what()));
                        json_object_array_add(jerr,eobj);
                        const char *errstr = json_object_to_json_string(jerr);
                        libhttppp::HttpResponse rep;
                        rep.setState(HTTP500);
                        rep.setContentType("application/json");
                        rep.send(curreq,errstr,strlen(errstr));
                        json_object_put(jerr);
                        return;
                    }catch(...){
                        libhttppp::HttpResponse rep;
                        rep.setState(HTTP500);
                        rep.setContentType("application/json");
                        rep.send(curreq,"[{\"error\":\"internal error loading domain\"}]",43);
                        return;
                    }

                    if(apidb){
                        AuthBackend::Guard guard(*apidb);
                        try{
                            ApiController(*apidb,did,curreq,tid,args);
                            g_Log.access(clientAddr,method,curreq.getRequestURL(),200);
                        }catch(std::exception &e){
                            g_Log.error(clientAddr,e.what());
                            g_Log.access(clientAddr,method,curreq.getRequestURL(),500);
                            json_object *jerr = json_object_new_array();
                            json_object *eobj = json_object_new_object();
                            json_object_object_add(eobj,"error",json_object_new_string(e.what()));
                            json_object_array_add(jerr,eobj);
                            const char *errstr = json_object_to_json_string(jerr);
                            libhttppp::HttpResponse rep;
                            rep.setState(HTTP500);
                            rep.setContentType("application/json");
                            rep.send(curreq,errstr,strlen(errstr));
                            json_object_put(jerr);
                        }catch(...){
                            g_Log.access(clientAddr,method,curreq.getRequestURL(),500);
                            libhttppp::HttpResponse rep;
                            rep.setState(HTTP500);
                            rep.setContentType("application/json");
                            rep.send(curreq,"[{\"error\":\"internal error\"}]",28);
                        }
                    }else{
                        g_Log.access(clientAddr,method,curreq.getRequestURL(),404);
                        libhttppp::HttpResponse rep;
                        rep.setState(HTTP404);
                        rep.setContentType("application/json");
                        rep.send(curreq,"[{\"error\":\"uuid not found\"}]",27);
                    }
                }else if(curreq.getRequestURL().compare(0,10,"/getavatar",10)==0){
                    try{
                        getAvatar(curreq,tid,args);
                        g_Log.access(clientAddr,method,curreq.getRequestURL(),200);
                    }catch(AuthBackendError &e){
                        g_Log.error(clientAddr,e.what());
                        g_Log.access(clientAddr,method,curreq.getRequestURL(),500);
                        libhttppp::HttpResponse rep;
                        rep.setState(HTTP500);
                        rep.setContentType("text/html");
                        rep.send(curreq,e.what(),strlen(e.what()));
                    }
                }else{
                    libhttppp::HttpResponse rep;
                    if(_AdminBackend.end()>sizeof(authdb::AuthHeader)){
                        rep.setState(HTTP307);
                        rep.setHeaderData("Location")->push_back("/settings/index");
                        rep.setContentType("text/html");
                        rep.send(curreq,"",0);
                    }else{
                        rep.setState(HTTP503);
                        rep.setContentType("text/html");
                        rep.send(curreq,"service not ready",17);
                    }
                }
            } catch (std::exception &e) {
                libhttppp::HttpResponse rep;
                rep.setState(HTTP500);
                rep.setContentType("text/html");
                rep.send(curreq,e.what(),strlen(e.what()));
            } catch (...) {
                libhttppp::HttpResponse rep;
                rep.setState(HTTP500);
                rep.setContentType("text/html");
                rep.send(curreq,"internal error",14);
            }
        }

        void getAvatar(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args){
            libhttppp::HttpResponse rep;

            AuthBackend *backend=nullptr;

            char domain[255],cuid[255],ext[16];
            if(sscanf(curreq.getRequestURL().c_str(),"/getavatar/%[^/]/%[^.].%s",domain,cuid,ext)<0){
                libhttppp::HttpResponse rep;
                rep.setState(HTTP400);
                rep.setContentType("text/html");
                rep.send(curreq,"bad avatar url",14);
                return;
            }

            ::uuid::uuid did;

            if(strcmp(domain,"admin") == 0 ){
                backend=&_AdminBackend;
            }else if(did.parse(domain) == 0 ){
                backend=getDomainBackend(did);
            }else{
                throw AuthBackendError("createUser: could not parse uuid!");
            }

            ::uuid::uuid uid(cuid);

            // Always lock the resolved backend
            AuthBackend::Guard domlock(*backend, AuthBackend::Shared);

            size_t rd=sizeof(authdb::AuthHeader),end=backend->end();

            while(rd+sizeof(AuthData::Record)<=end){
                std::shared_ptr <authdb::AuthData::Record> cur=std::make_shared<authdb::AuthData::Record>();
                backend->setPos(rd);
                backend->read((unsigned char*)cur.get(),sizeof(AuthData::Record));
                size_t next=backend->getPos()+cur->datasize;
                if(next<backend->getPos() || next>end) break;
                rd=next;

                if(uid==cur->ruid && strcmp(cur->fieldname,"avatar")==0){
                    cur->data = new char[cur->datasize + 1];
                    backend->read((unsigned char*)cur->data,cur->datasize);
                    cur->data[cur->datasize] = '\0';
                    char ctype[255];
                    snprintf(ctype,255,"image/%s",ext);
                    rep.setContentType(ctype);
                    std::vector<char> img;
                    std::copy(cur->data,cur->data+cur->datasize,std::back_inserter(img));
                    rep.send(curreq,img,cur->datasize);
                    delete[] cur->data;
                    return;
                }
            }

            if(end!=0){
                rep.setState(HTTP404);
                rep.send(curreq,"",0);
            }

        };

        AuthBackend *getDomainBackend(const ::uuid::uuid &did){
            {
                std::shared_lock<std::shared_mutex> rl(_DomainCacheMtx);
                auto it = _DomainCache.find(std::string(did.value, did.value+16));
                if(it != _DomainCache.end())
                    return it->second.get();
            }

            DomainBackend db(_AdminBackend);
            DomainInfo dinfo;
            {
                AuthBackend::Guard admlock(_AdminBackend, AuthBackend::Shared);
                dinfo = db.resolve(did.value);
            }
            if(!dinfo.found)
                return nullptr;

            auto backend = std::make_unique<AuthBackend>(
                dinfo.storageType, dinfo.storageOptions, dinfo.domainName);

            std::unique_lock<std::shared_mutex> wl(_DomainCacheMtx);
            auto key = std::string(did.value, did.value+16);
            auto [it, inserted] = _DomainCache.emplace(key, std::move(backend));
            return it->second.get();
        }

    private:
        AuthBackend        &_AdminBackend;
        std::unique_ptr<Session>  _Session;
        AdminInterface       _AdminInterface;
        std::vector<char>  _AuthJs;
        std::vector<char>  _AuthCss;
        std::shared_mutex                                     _DomainCacheMtx;
        std::map<std::string, std::unique_ptr<AuthBackend>>   _DomainCache;
    };

    int searchValue(authdb::AuthBackend &backend,const char*fieldname,const char *value){
        authdb::AuthData::Record *user=new AuthData::Record;
        size_t rd=sizeof(authdb::AuthHeader),brd=rd;
        while(rd<backend.end()){
            backend.setPos(rd);
            backend.read((unsigned char*)user,sizeof(AuthData::Record));
            rd=backend.getPos()+user->datasize;
            if(strcmp(user->fieldname,fieldname) == 0){
                user->data = new char[user->datasize];
                backend.read((unsigned char*)user->data,user->datasize);
                if(strcmp(user->data,value) == 0){
                    delete[] user->data;
                    user->data=nullptr;
                    goto VALUEFOUND;
                }
                delete[] user->data;
                user->data=nullptr;
            }
            brd=rd;
        }
VALUEFOUND:
        delete user;
        return brd;
    }

};

int main(int argc,char *argv[]){
    cmdplus::CmdController cmd(cmdplus::CmdController::getInstance());

    cmd.registerCmd("config",'c',true,"","Config Path");
    cmd.registerCmd("debug",'d',false,"false","Debug Mode");
#ifndef _WIN32
    cmd.registerCmd("foreground",'f',false,"false","Run in foreground (disable daemon)");
#endif

    cmd.parseCmd(argc,argv);

    if(!cmd.checkRequired()){
        std::cerr << "Config Path required !" << std::endl;
        cmd.printHelp();
        return -1;
    }

#ifndef _WIN32
    bool foreground = (cmd.getCmdbyKey("foreground")->getValue() == "true");

    if (!foreground) {
        pid_t pid = fork();
        if (pid < 0) {
            std::cerr << "fork failed" << std::endl;
            return 1;
        }
        if (pid > 0)
            return 0;

        if (setsid() < 0) {
            std::cerr << "setsid failed" << std::endl;
            return 1;
        }

        umask(0);

        int devnull = open("/dev/null", O_RDWR);
        if (devnull >= 0) {
            dup2(devnull, STDIN_FILENO);
            dup2(devnull, STDOUT_FILENO);
            dup2(devnull, STDERR_FILENO);
            if (devnull > STDERR_FILENO)
                close(devnull);
        }
    }

#endif

    /* Create log directory and open log files */
#ifndef _WIN32
    {
        struct stat st;
        if (stat("/var/log/authdb", &st) != 0)
            mkdir("/var/log/authdb", 0750);
        struct passwd *lpw = getpwnam("authdb");
        if (lpw)
            chown("/var/log/authdb", lpw->pw_uid, lpw->pw_gid);
    }
    authdb::g_Log.open("/var/log/authdb/access.log", "/var/log/authdb/error.log");
#else
    authdb::g_Log.open("access.log", "error.log");
#endif

    try {

    confplus::Config config(cmd.getCmdbyKey("config")->getValue());

    /* Determine backend type: cluster or file */
    bool clusterEnabled = false;
    {
        auto *clusterEnable = config.getKey("/AUTHDB/CLUSTER/ENABLE");
        if (clusterEnable && config.getValue(clusterEnable, 0) == "true")
            clusterEnabled = true;
    }
        std::string cert=config.getValue(config.getKey("/AUTHDB/SSL/CERT"),0);
        std::string key=config.getValue(config.getKey("/AUTHDB/SSL/KEY"),0);

        /* Cluster config — parsed early, started after priv drop */
        authdb::Cluster cluster;
        authdb::ClusterConfig ccfg;
        if (clusterEnabled) {
            ccfg.bind_address = config.getValue(config.getKey("/AUTHDB/CLUSTER/BIND"), 0);
            ccfg.port = config.getIntValue(config.getKey("/AUTHDB/CLUSTER/PORT"), 0);
            ccfg.cert_file = cert;
            ccfg.key_file = key;

            auto *authName = config.getKey("/AUTHDB/CLUSTER/AUTH_NAME");
            auto *authKey  = config.getKey("/AUTHDB/CLUSTER/AUTH_KEY");
            if (authName) ccfg.client_name = config.getValue(authName, 0);
            if (authKey)  ccfg.client_key  = config.getValue(authKey, 0);

            auto *dataBlocks   = config.getKey("/AUTHDB/CLUSTER/DATA_BLOCKS");
            auto *parityBlocks = config.getKey("/AUTHDB/CLUSTER/PARITY_BLOCKS");
            if (dataBlocks)   ccfg.data_blocks   = config.getIntValue(dataBlocks, 0);
            if (parityBlocks) ccfg.parity_blocks = config.getIntValue(parityBlocks, 0);

            auto *storePath = config.getKey("/AUTHDB/CLUSTER/PATH");
            if (storePath) ccfg.store_path = config.getValue(storePath, 0);

            auto *peersKey = config.getKey("/AUTHDB/CLUSTER/PEERS");
            if (peersKey) {
                for (int pi = 0; ; pi++) {
                    try {
                        std::string peerStr = config.getValue(peersKey, pi);
                        if (peerStr.empty()) break;
                        auto colon = peerStr.rfind(':');
                        if (colon != std::string::npos) {
                            authdb::ClusterNode node;
                            node.address = peerStr.substr(0, colon);
                            node.port = std::stoi(peerStr.substr(colon + 1));
                            ccfg.peers.push_back(node);
                        }
                    } catch (...) { break; }
                }
            }
        }

        std::string backendPath;
        if (!clusterEnabled) {
            backendPath = config.getValue(config.getKey("/AUTHDB/ADMINDB/PATH"), 0);
        }

        libhttppp::HttpD httpd(
            config.getValue(config.getKey("/AUTHDB/BIND"),0),
            config.getIntValue(config.getKey("/AUTHDB/PORT"),0),
            config.getIntValue(config.getKey("/AUTHDB/MAXCONN"),0),
            cert,
            key
        );

        /* Start cluster while still root so QUIC can bind privileged ports */
        if (clusterEnabled) {
            cluster.init(ccfg);
            cluster.start();
            authdb::g_Cluster = &cluster;
            std::cerr << "Cluster started on " << ccfg.bind_address << ":" << ccfg.port
                      << " with " << ccfg.peers.size() << " peer(s)" << std::endl;
            // Wait for peers to become reachable before constructing backends.
            // This ensures fetchFromCluster() in ClusterBackend can retrieve data.
            size_t online = cluster.waitForPeers(120);
            if (online == 0) {
                std::cerr << "Cluster: no peers reachable, cannot start. Exiting." << std::endl;
                return 1;
            }
        }

        // Instantiate session store: cluster-aware or local-only
        std::unique_ptr<authdb::Session> sessionStore;
        if (clusterEnabled) {
            sessionStore = std::make_unique<authdb::ClusterSession>();
        } else {
            sessionStore = std::make_unique<authdb::LocalSession>();
        }
        authdb::AuthSession = sessionStore.get();

        authdb::AuthBackend backend(
            clusterEnabled ? authdb::AuthBackendType::ClusterStore : authdb::AuthBackendType::File,
            backendPath,
            "admin.local"
        );

        authdb::AuthDB authdb(backend,httpd.getServerSockets());

        if(cmd.getCmdbyKey("debug")->getValue()=="true"){
            authdb::DEBUG=true;
            authdb.threads=1;
        }

        /* Load plugins from configured directory */
        std::string pluginDir = config.getValue(config.getKey("/AUTHDB/PLUGINS/PATH"), 0);

        /* Build JSON config string for plugins to parse */
        json_object *jcfg = json_object_new_object();
        {
            auto *kdcKey = config.getKey("/AUTHDB/KDC/BIND");
            auto *kdcPort = config.getKey("/AUTHDB/KDC/PORT");
            auto *maxConn = config.getKey("/AUTHDB/MAXCONN");
            if (kdcKey)
                json_object_object_add(jcfg, "bind",
                    json_object_new_string(config.getValue(kdcKey, 0).c_str()));
            if (kdcPort)
                json_object_object_add(jcfg, "port",
                    json_object_new_int(config.getIntValue(kdcPort, 0)));
            if (maxConn)
                json_object_object_add(jcfg, "maxconn",
                    json_object_new_int(config.getIntValue(maxConn, 0)));

            /* AD/LDAP plugin config */
            auto *adBind = config.getKey("/AUTHDB/AD/BIND");
            auto *adPort = config.getKey("/AUTHDB/AD/PORT");
            auto *adMax  = config.getKey("/AUTHDB/AD/MAXCONN");
            if (adBind)
                json_object_object_add(jcfg, "ad_bind",
                    json_object_new_string(config.getValue(adBind, 0).c_str()));
            if (adPort)
                json_object_object_add(jcfg, "ad_port",
                    json_object_new_int(config.getIntValue(adPort, 0)));
            if (adMax)
                json_object_object_add(jcfg, "ad_maxconn",
                    json_object_new_int(config.getIntValue(adMax, 0)));
        }
        const char *cfgStr = json_object_to_json_string(jcfg);

        authdb::PluginManager pluginMgr;
        authdb::g_PluginManager = &pluginMgr;

        if (!pluginDir.empty()) {
            int n = pluginMgr.loadAll(pluginDir, backend, cfgStr);
            std::cerr << "Loaded " << n << " plugin(s) from " << pluginDir << std::endl;
        }
        json_object_put(jcfg);

        /* Wire up well-known plugin interfaces */
        authdb::g_KdcPlugin = pluginMgr.getPlugin<authdb::KdcPlugin>("krb5");
        authdb::g_AdPlugin  = pluginMgr.getPlugin<authdb::AdPlugin>("ad");

        /* Init plugins while still root — bind privileged ports (88, 389, etc.) */
        pluginMgr.initAll();

        /* Drop privileges after all sockets are bound */
#ifndef _WIN32
        if (getuid() == 0) {
            struct passwd *pw = getpwnam("authdb");
            if (pw) {
                if (setgid(pw->pw_gid) != 0) {
                    std::cerr << "setgid failed" << std::endl;
                    return 1;
                }
                if (initgroups("authdb", pw->pw_gid) != 0) {
                    std::cerr << "initgroups failed" << std::endl;
                    return 1;
                }
                if (setuid(pw->pw_uid) != 0) {
                    std::cerr << "setuid failed" << std::endl;
                    return 1;
                }
            } else {
                std::cerr << "user authdb not found, running as root" << std::endl;
            }
        }
#endif

        /* Start each plugin in its own background thread (runs as unprivileged user) */
        std::vector<std::thread> pluginThreads;
        for (auto &[pname, lp] : pluginMgr.plugins()) {
            pluginThreads.emplace_back([&lp, &pname](){
                try {
                    lp.instance->run();
                } catch(const netplus::NetException &e) {
                    std::cerr << "Plugin '" << pname << "' net error: "
                              << e.what() << std::endl;
                } catch(const std::exception &e) {
                    std::cerr << "Plugin '" << pname << "' error: "
                              << e.what() << std::endl;
                } catch(...) {
                    std::cerr << "Plugin '" << pname << "' unknown error" << std::endl;
                }
            });
            pluginThreads.back().detach();
        }

        authdb.runEventloop();

        /* Shutdown cluster */
        authdb::g_Cluster = nullptr;
        cluster.stop();

        authdb::g_KdcPlugin = nullptr;
        authdb::g_AdPlugin  = nullptr;
        authdb::g_PluginManager = nullptr;
    }catch(const netplus::NetException &e){
        std::cerr << "NetException: " << e.what() << std::endl;
    }catch(authdb::AuthBackendError &e){
        std::cerr << e.what() << std::endl;
    }catch(std::exception &e){
        std::cerr << e.what() << std::endl;
    }catch(...){
        std::cerr << "Unknown fatal exception" << std::endl;
    }
    return 0;
}
