/*
 * © Copyright 1996-2012 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "mars.h"
#include "mars_client_version.h"

void add_groups(const char* u, request* r) {
#ifdef ECMWF
    struct group* gr;
    char** cp;
    int found = false;

    setgrent();
    while ((gr = getgrent())) {

        for (cp = gr->gr_mem; cp && *cp; cp++)
            if (EQ(*cp, u)) {
                add_value(r, "group", "%s", gr->gr_name);
                found = true;
            }
    }

    if (!found) {
        FILE* f = mail_open(mars.dhsmail, "getgrent failed for %s", u);
        mail_request(f, "Environment :", r);
        mail_close(f);

        marslog(LOG_WARN, "Unable to get group information, getgrent");
        add_value(r, "group", "unknown");
    }

    endgrent();
#endif
}

#ifdef ECMWF
#define READ_EMS_MAX_TRY 3
static void read_ems_cache(const char* who, request* env) {
    char buf[1024];
    FILE* in = NULL;
    typedef enum reason
    {
        notfound,
        cantopen
    } reason;
    reason why;
    int retry = 1;

    /* Try to read cached file several times, in case of updates happenning */
    while (retry++ < READ_EMS_MAX_TRY) {
        if ((in = fopen(mars.emsfile, "r")) != NULL) {
            while (fgets(buf, sizeof(buf) - 1, in)) {
                char user[1024];
                char category[1024];
                sscanf(buf, "%s %s", user, category);
                if (EQ(user, who)) {
                    marslog(LOG_DBUG, "Using EMS categories '%s'", category);
                    set_list(env, "category", category);
                    fclose(in);
                    return;
                }
            }

            fclose(in);
            if (mars.marslite_mode == 1) {
                const char* c = getenv("MARS_EMS_CATEGORY_MARSLITE_MODE");
                if (c == NULL)
                    c = "public";
                set_list(env, "category", c);
                return;
            }

            marslog(LOG_WARN, "Can't find EMS categories for user '%s'", who);
            why = notfound;
        }
        else {
            marslog(LOG_EROR | LOG_PERR, "Cannot open %s", mars.emsfile);
            why = cantopen;
        }
        sleep(2);
    }

    /* Inform about problems with EMS */
    {
        int e   = errno;
        FILE* f = mail_open(mars.dhsmail, "read_ems_cache failed for %s", who);
        switch (why) {
            case notfound:
                mail_msg(f, "Can't find EMS categories for user '%s'", who);
                mail_msg(f, "in EMS file %s", mars.emsfile);
                break;
            case cantopen:
                mail_msg(f, "Cannot open %s", mars.emsfile);
                if (e)
                    mail_msg(f, "(%s)", strerror(e));
                break;
            default:
                mail_msg(f, "Unknown reason (%d) for user '%s'", why, who);
                break;
        }
        mail_request(f, "Environment :", env);
        mail_close(f);
    }

    marslog(LOG_WARN, "Using NIS group information");
    /* temp fix */ add_groups(who, env);
}


#define READ_EMS_ACCOUNT_MAX_TRY 5
static void read_ems_accounts(const char* who, request* env) {
    char buf[1024];
    FILE* in = NULL;
    typedef enum reason
    {
        notfound,
        cantopen
    } reason;
    reason why;
    int retry = 1;

    /* Try to read cached file several times, in case of updates happenning */
    while (retry++ < READ_EMS_ACCOUNT_MAX_TRY) {
        if ((in = fopen(mars.emsaccountsfile, "r")) != NULL) {
            while (fgets(buf, sizeof(buf) - 1, in)) {
                char user[1024];
                char account[1024];
                sscanf(buf, "%s %s", user, account);
                if (EQ(user, who)) {
                    marslog(LOG_DBUG, "Using EMS accounts '%s'", account);
                    set_list(env, "account", account);
                    fclose(in);
                    return;
                }
            }

            fclose(in);
            if (mars.marslite_mode == 1) {
                const char* c = getenv("MARS_EMS_ACCOUNT_MARSLITE_MODE");
                if (c == NULL)
                    c = "dspublic";
                set_list(env, "account", c);
                return;
            }
            marslog(LOG_WARN, "Can't find EMS accounts for user '%s'", who);
            why = notfound;
        }
        else {
            marslog(LOG_EROR | LOG_PERR, "Cannot open %s", mars.emsaccountsfile);
            why = cantopen;
        }
        sleep(2);
    }

    /* Inform about problems with EMS */
    {
        if (mars.enable_ecaccount_email) {
            int e   = errno;
            FILE* f = mail_open(mars.dhsmail, "read_ems_accounts failed for %s", who);
            switch (why) {
                case notfound:
                    mail_msg(f, "Can't find EMS accounts for user '%s'", who);
                    mail_msg(f, "in EMS file %s", mars.emsaccountsfile);
                    break;
                case cantopen:
                    mail_msg(f, "Cannot open %s", mars.emsaccountsfile);
                    if (e > 0)
                        mail_msg(f, "(%s)", strerror(e));
                    break;
                default:
                    mail_msg(f, "Unknown reason (%d) for user '%s'", why, who);
                    break;
            }
            mail_request(f, "Environment :", env);
            mail_close(f);
        }
    }

    /* temp fix */
    set_value(env, "account", "unknown");
}

#endif
static request* env = NULL;

#ifdef ECMWF
boolean user_in_ABC_account(const char* user, const char* account, const request* env) {
    int i         = 0;
    boolean match = false;
    const char* v = NULL;

    while ((v = get_value(env, "account", i++)) != 0) {
        if (EQ(account, v))
            match = true;
    }
    return match;
}

#endif

int startswith(const char* pre, const char* str) {
    return strncmp(pre, str, strlen(pre)) == 0;
}

void add_mars_environ(request* env) {
    extern char** environ; /* all environment variables */
    char* kv    = NULL;
    char* value = NULL;
    int i       = 0;
    static char key[1024];
    while (environ[i]) {
        kv = environ[i++]; /* string in form "key=value" */
        /* parse key=value */
        if (startswith("MARS_ENVIRON_", kv)) {
            value = strchr(kv, '=');
            if (value) {
                memset(key, 0, sizeof(key));
                strncpy(key, kv + 13, value - (kv + 13)); /* remove MARS_ENVIRON_ */
                value++;
                // printf("key '%s' -> value '%s'\n", key, value);
                set_value(env, lowcase(key), "%s", value);
            }
        }
    }
}

request* get_environ(void) {
    char buf[1024];
    unsigned long id;
    char* useWebmars     = getenv("WEBMARS_USER");
    char* emosChangeUser = getenv("EMOS_CHANGE_USER");
    const char* who      = user(NULL);
    char* useTest        = getenv("MARS_ENV");
    const char* abc      = strcache(getenv("ECACCOUNT"));
    const char* owner    = strcache(getenv("ECREALOWNER"));
    char* environment    = getenv("ENVIRONMENT");
    char* token          = getenv("MARS_USER_TOKEN");
    char* email          = getenv("MARS_USER_EMAIL");
    char* marsClientHost = getenv("MARS_CLIENT_HOSTNAME");
    char* origin         = getenv("MARS_ORIGIN");
    char* caller         = getenv("MARS_CALLER");

    if (env)
        return env;

    if ((EQ(who, "bamboo") || EQ(who, "deploy") || (EQ(who, "worker")) && !mars.marslite_mode)) {
        who = user("max");
    }

    if (useTest && EQ(who, "max")) {
        env = read_request_file(useTest);
        marslog(LOG_WARN, "User %s has changed the environment", who);
        print_all_requests(env);
        return env;
    }

    env = new_request(strcache("environ"), NULL);

    add_mars_environ(env);

    set_value(env, "user", who);

#if defined(ECMWF) && !defined(NOEMS)
    if ((useWebmars && EQ(who, "max")) || (emosChangeUser && EQ(who, "emos")) || (useWebmars && EQ(who, "worker") && mars.marslite_mode)) {
        char* u = useWebmars;
        if (emosChangeUser)
            u = emosChangeUser;
        set_value(env, "user", u);
        if (getenv("MARS_EMS_CATEGORIES")) {
            if (mars.show_hosts)
                marslog(LOG_INFO, "Using EMS categories '%s'", getenv("MARS_EMS_CATEGORIES"));
            set_list(env, "category", getenv("MARS_EMS_CATEGORIES"));
        }
        else {
            if (mars.show_hosts)
                marslog(LOG_WARN, "User '%s' executes request as user '%s'", who, u);
            if (mars.emsfile)
                read_ems_cache(u, env);
            else
                add_groups(u, env);
        }
        if (mars.show_hosts)
            print_all_requests(env);
        who = u;
    }
    else {
        set_value(env, "user", who);
        if (mars.emsfile)
            read_ems_cache(who, env);
        else
            add_groups(who, env);
    }

    if (mars.debug_ems) {
        marslog(LOG_INFO, "MARS environment for '%s':", who);
        print_all_requests(env);
    }

    if (owner)
        set_value(env, "owner", "%s", owner);


    if (abc) {
        const char* name = who;
        if (mars.emsaccountsfile) {
            if (owner)
                name = owner;
            read_ems_accounts(name, env);
        }

        /* Check that the value provided for this user is valid */
        if (!user_in_ABC_account(name, abc, env)) {
            if (mars.enable_ecaccount_email) {
                FILE* f = mail_open(mars.dhsmail, "MARS ABC account mismatch for user %s [%s] , account %s", name, who, abc);
                mail_request(f, "Environment :", env);
                mail_close(f);
            }
            /* For now, log users and continue even if their account doesn't match */
        }
        set_value(env, "abc", abc);
    }

    /* No EMS category. Stop here */
    if (get_value(env, "category", 0) == NULL) {
        marslog(LOG_WARN, "Unable to find EMS category for user '%s'", who);
        if (mars.enforce_ems_category) {
            marslog(LOG_EROR, "MARS cannot continue without an EMS category");
            marslog(LOG_EROR, "Please, contact the MARS team. Exit");
            marsexit(1);
        }
        else
            marslog(LOG_WARN, "MARS has been configured to continue even without EMS category");
    }

    if (mars.marslite_mode && mars.webmars_request_id) {
        marslog(LOG_INFO, "Web API request id: %s", mars.webmars_request_id);
        set_value(env, "wrid", mars.webmars_request_id);
    }
#endif

    if (environment)
        set_value(env, "environment", environment);

#ifdef METVIEW
    set_value(env, "client", "metview");
#else
    set_value(env, "client", "mars-client");
#endif

    if (origin)
        set_value(env, "origin", origin);

    if (caller)
        set_value(env, "caller", caller);

    if (gethostname(buf, sizeof(buf)) == 0)
        set_value(env, "host", buf);

    if (marsClientHost)
        set_value(env, "host", marsClientHost);

    // if(getdomainname(buf,sizeof(buf)) == 0)
    // 	set_value(env,"domain",buf);

    // set_value(env,"sourcebranch","%s",marssourcebranch());

    // #ifndef hpux
    // 	id = (unsigned long)gethostid();
    // 	sprintf(buf,"%lu.%lu.%lu.%lu",
    // 		(id & 0xff000000) >> 24,
    // 		(id & 0x00ff0000) >> 16,
    // 		(id & 0x0000ff00) >>  8,
    // 		(id & 0x000000ff));

    // 	add_value(env,"host",buf);
    // #endif

    if (token)
        set_value(env, "token", "%s", token);

    if (email)
        set_value(env, "email", "%s", email);


    set_value(env, "version", "%s", mars_client_bundle_version_str());

    set_value(env, "pid", "%d", getpid());

    if (mars.debug) {
        marslog(LOG_DBUG, "Environment is:");
        print_all_requests(env);
    }

    return env;
}

void set_environ(request* e) {
    free_all_requests(env);
    env = clone_all_requests(e);
}

void new_user(const char* u) {
    request* e = get_environ();
    set_value(e, "user", "%s", u);
    unset_value(e, "group");
    add_groups(u, e);
#ifdef ECMWF
    if (mars.emsfile)
        read_ems_cache(u, e);
    if (mars.emsaccountsfile)
        read_ems_accounts(u, e);
    if (mars.force_setting_ecaccount) {
        const char* v = NULL;
        if (v = get_value(e, "account", 0))
            set_value(e, "abc", v);
    }
#endif
    set_value(e, "pid", "%d", getpid());
}
