#include <stdio.h>
#include <stdlib.h>
#include <cumacros.h>
#include <cumbia.h>
#include <cueventloop.h>
#include <signal.h>
#include <cuserviceprovider.h>
#include <cuthreadfactoryimpl.h>
#include <cuthreadseventbridge.h>
#include <pwd.h>
#include <unistd.h>
#include <calog.h>

#include "config.h"
#include "ca-tango-db-cache-mgr.h"

#include <ca-receiver-a.h>
#include <ca-opt-parser.h>

CuEventLoopService *loo_s = nullptr;

void on_int(int signo) {
    if(signo == SIGINT || signo == SIGTERM) {
        if(loo_s)
            loo_s->exit();
    }
}

int drop_privileges(const char* user, char* msg) {
    struct passwd *p = getpwnam(user);
    if(p == nullptr) {
        snprintf(msg, 512, "failed to find UID for user \"%s\": %s", user, strerror(errno));
        return 1;
    }
    if(setgid(p->pw_gid) != 0) {
        snprintf(msg, 512, "unable to drop group privileges to GID %d: %s", p->pw_gid, strerror(errno));
        return 1;
    }
    if(setuid(p->pw_uid) != 0) {
        snprintf(msg, 512, "unable to drop user privileges to UID %d: %s", p->pw_uid, strerror(errno));
        return 1;
    }
    if (setuid(0) != -1)  {
        snprintf(msg, 512, "setuid back to zero succeeded, quitting as this is a security risk");
        return 1;
    }
    msg[0] = '\0';
    return 0;
}

int main(int argc, char *argv[]) {
    CaOptParser options;
    CuData opts = options.get_options(argc, argv, "ca-tango-db-cache-mgr");
    if(options.errors.size() > 0) {
        perr("main.cpp: error in command line args: \n");
        for(const std::string& e : options.errors)
            perr("- %s\n", e.c_str());
        return EXIT_FAILURE;
    }

    // logging - 1. level
    CaLogFactory logfa;
    CuLogImplI *log_i = nullptr;
    CuLog::Level logle = static_cast<CuLog::Level>(opts["log_level"].toInt());

    // logging - 2. where
    // drop privileges after this so that the log file can be created anywhere
    // CaFLog will change the file ownership to the unprivileged user after creation
    if(opts.has("log_where", "syslog")) log_i = logfa.create(CaLogFactory::Syslog, logle);
    else if(opts["log_where"].toString() != "console") log_i = logfa.create(CaLogFactory::LogFile, logle, opts["log_where"].toString().c_str(), opts.containsKey("user") ? vtoc2(opts, "user") : nullptr);
    else log_i = logfa.create(CaLogFactory::LogConsole, logle);
    // end logging setup

    log_i->write("main.cpp", "enter: " + std::string(argv[0]) + " version " + std::string(CATANGODBCACHEMGR_VERSION), CuLog::LevelAll);

    if(getuid() == 0 && !opts.containsKey("user")) {
        log_i->write("main.cpp", "cannot run as root and \"-s username\" command line argument missing", CuLog::LevelError);
        return EXIT_FAILURE;
    }
    else if(getuid() == 0 && opts.containsKey("user")) {
        char msg[512];
        if(drop_privileges(vtoc2(opts, "user"), msg) != 0) {
            log_i->write("main.cpp", msg, CuLog::LevelError);
            return EXIT_FAILURE;
        }
    }

    printf("main.cpp: options %s\n", datos(opts));
    if(!opts.containsKey("redis-host") || !opts.containsKey("host") || !opts.containsKey("port")) {
        perr("main.cpp: one or more options \"redis-host=\", \"host\" or \"port\" missing in the configuration file \"%s\"",
             vtoc2(opts, "dbfile"));
        return EXIT_FAILURE;
    }
    else {

        signal(SIGINT, on_int);
        signal(SIGTERM, on_int);

        Cumbia *cumbia = new Cumbia();
        // The event loop is a single object shared across all Cumbia instances
        loo_s = new CuEventLoopService();
        // register the service with the shared option set to true
        cumbia->getServiceProvider()->registerSharedService(CuServices::EventLoop, loo_s);

        CuData camgrtok("type", "catgcachemgr");
        camgrtok.merge(opts);
        CuThreadFactoryImpl* thf_impl = new CuThreadFactoryImpl;
        CuThreadsEventBridgeFactory *thread_eb_f = new CuThreadsEventBridgeFactory;

        CaTgDbCacheMgr *cachemgr = new CaTgDbCacheMgr(cumbia, loo_s, opts, log_i);

        CaReceiver_A *server_a = nullptr;
        printf("\e[1;32m # \e[0mmain.cpp: starting receiver on %s\n", datos(opts));
        server_a = new CaReceiver_A(opts);
        cumbia->registerActivity(server_a, cachemgr, CuData("type", "catgcachemgr-receiver"), *thf_impl, *thread_eb_f);

        printf("\e[1;32m # \e[0mmain.cpp: \e[1;32mca-tango-db-cache-mgr version \e[2;32m%s\e[0m started\n", CATANGODBCACHEMGR_VERSION);
        loo_s->exec(false);
        server_a->stop();
        cumbia->unregisterActivity(server_a);
        delete cumbia;
        printf("\e[1;32m # \e[0mmain.cpp: ca-tango-db-cache-mgr exiting\n");
        log_i->write("ca-tango-db-cache-mgr", "exiting", CuLog::LevelAll);
        delete log_i;
    }
}