// SPDX-License-Identifier: GPL-3.0-or-later

#include "claim.h"
#include "registry/registry_internals.h"
#include "aclk/aclk.h"
#include "aclk/aclk_proxy.h"

char *claiming_pending_arguments = NULL;

static char *claiming_errors[] = {
        "Agent claimed successfully",                   // 0
        "Unknown argument",                             // 1
        "Problems with claiming working directory",     // 2
        "Missing dependencies",                         // 3
        "Failure to connect to endpoint",               // 4
        "The CLI didn't work",                          // 5
        "Wrong user",                                   // 6
        "Unknown HTTP error message",                   // 7
        "invalid node id",                              // 8
        "invalid node name",                            // 9
        "invalid room id",                              // 10
        "invalid public key",                           // 11
        "token expired/token not found/invalid token",  // 12
        "already claimed",                              // 13
        "processing claiming",                          // 14
        "Internal Server Error",                        // 15
        "Gateway Timeout",                              // 16
        "Service Unavailable",                          // 17
        "Agent Unique Id Not Readable"                  // 18
};

/* Retrieve the claim id for the agent.
 * Caller owns the string.
*/
char *get_agent_claimid()
{
    char *result;
    rrdhost_aclk_state_lock(localhost);
    result = (localhost->aclk_state.claimed_id == NULL) ? NULL : strdupz(localhost->aclk_state.claimed_id);
    rrdhost_aclk_state_unlock(localhost);
    return result;
}

#define CLAIMING_COMMAND_LENGTH 16384
#define CLAIMING_PROXY_LENGTH CLAIMING_COMMAND_LENGTH/4

extern struct registry registry;

/* rrd_init() and post_conf_load() must have been called before this function */
void claim_agent(char *claiming_arguments)
{
    if (!netdata_cloud_setting) {
        error("Refusing to claim agent -> cloud functionality has been disabled");
        return;
    }

#ifndef DISABLE_CLOUD
    int exit_code;
    pid_t command_pid;
    char command_buffer[CLAIMING_COMMAND_LENGTH + 1];
    FILE *fp_child_output, *fp_child_input;

    // This is guaranteed to be set early in main via post_conf_load()
    char *cloud_base_url = appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", NULL);
    if (cloud_base_url == NULL)
        fatal("Do not move the cloud base url out of post_conf_load!!");
    const char *proxy_str;
    ACLK_PROXY_TYPE proxy_type;
    char proxy_flag[CLAIMING_PROXY_LENGTH] = "-noproxy";

    proxy_str = aclk_get_proxy(&proxy_type);

    if (proxy_type == PROXY_TYPE_SOCKS5 || proxy_type == PROXY_TYPE_HTTP)
        snprintf(proxy_flag, CLAIMING_PROXY_LENGTH, "-proxy=\"%s\"", proxy_str);

    snprintfz(command_buffer,
              CLAIMING_COMMAND_LENGTH,
              "exec netdata-claim.sh %s -hostname=%s -id=%s -url=%s -noreload %s",

              proxy_flag,
              netdata_configured_hostname,
              localhost->machine_guid,
              cloud_base_url,
              claiming_arguments);

    info("Executing agent claiming command 'netdata-claim.sh'");
    fp_child_output = netdata_popen(command_buffer, &command_pid, &fp_child_input);
    if(!fp_child_output) {
        error("Cannot popen(\"%s\").", command_buffer);
        return;
    }
    info("Waiting for claiming command to finish.");
    while (fgets(command_buffer, CLAIMING_COMMAND_LENGTH, fp_child_output) != NULL) {;}
    exit_code = netdata_pclose(fp_child_input, fp_child_output, command_pid);
    info("Agent claiming command returned with code %d", exit_code);
    if (0 == exit_code) {
        load_claiming_state();
        return;
    }
    if (exit_code < 0) {
        error("Agent claiming command failed to complete its run.");
        return;
    }
    errno = 0;
    unsigned maximum_known_exit_code = sizeof(claiming_errors) / sizeof(claiming_errors[0]) - 1;

    if ((unsigned)exit_code > maximum_known_exit_code) {
        error("Agent failed to be claimed with an unknown error.");
        return;
    }
    error("Agent failed to be claimed with the following error message:");
    error("\"%s\"", claiming_errors[exit_code]);
#else
    UNUSED(claiming_arguments);
    UNUSED(claiming_errors);
#endif
}

#ifdef ENABLE_ACLK
extern int aclk_connected, aclk_kill_link, aclk_disable_runtime;
#endif

/* Change the claimed state of the agent.
 *
 * This only happens when the user has explicitly requested it:
 *   - via the cli tool by reloading the claiming state
 *   - after spawning the claim because of a command-line argument
 * If this happens with the ACLK active under an old claim then we MUST KILL THE LINK
 */
void load_claiming_state(void)
{
    // --------------------------------------------------------------------
    // Check if the cloud is enabled
#if defined( DISABLE_CLOUD ) || !defined( ENABLE_ACLK )
    netdata_cloud_setting = 0;
#else
    uuid_t uuid;

    // Propagate into aclk and registry. Be kind of atomic...
    appconfig_get(&cloud_config, CONFIG_SECTION_GLOBAL, "cloud base url", DEFAULT_CLOUD_BASE_URL);

    rrdhost_aclk_state_lock(localhost);
    if (localhost->aclk_state.claimed_id) {
        if (aclk_connected)
            localhost->aclk_state.prev_claimed_id = strdupz(localhost->aclk_state.claimed_id);
        freez(localhost->aclk_state.claimed_id);
        localhost->aclk_state.claimed_id = NULL;
    }
    if (aclk_connected)
    {
        info("Agent was already connected to Cloud - forcing reconnection under new credentials");
        aclk_kill_link = 1;
    }
    aclk_disable_runtime = 0;

    char filename[FILENAME_MAX + 1];
    snprintfz(filename, FILENAME_MAX, "%s/cloud.d/claimed_id", netdata_configured_varlib_dir);

    long bytes_read;
    char *claimed_id = read_by_filename(filename, &bytes_read);
    if(claimed_id && uuid_parse(claimed_id, uuid)) {
        error("claimed_id \"%s\" doesn't look like valid UUID", claimed_id);
        freez(claimed_id);
        claimed_id = NULL;
    }

    if(claimed_id) {
        localhost->aclk_state.claimed_id = mallocz(UUID_STR_LEN);
        uuid_unparse_lower(uuid, localhost->aclk_state.claimed_id);
    }

    invalidate_node_instances(&localhost->host_uuid, claimed_id ? &uuid : NULL);
    metaqueue_store_claim_id(&localhost->host_uuid, claimed_id ? &uuid : NULL);

    rrdhost_aclk_state_unlock(localhost);
    if (!claimed_id) {
        info("Unable to load '%s', setting state to AGENT_UNCLAIMED", filename);
        return;
    }

    freez(claimed_id);

    info("File '%s' was found. Setting state to AGENT_CLAIMED.", filename);
    netdata_cloud_setting = appconfig_get_boolean(&cloud_config, CONFIG_SECTION_GLOBAL, "enabled", 1);
#endif
}

struct config cloud_config = { .first_section = NULL,
                               .last_section = NULL,
                               .mutex = NETDATA_MUTEX_INITIALIZER,
                               .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare },
                                          .rwlock = AVL_LOCK_INITIALIZER } };

void load_cloud_conf(int silent)
{
    char *filename;
    errno = 0;

    int ret = 0;

    filename = strdupz_path_subpath(netdata_configured_varlib_dir, "cloud.d/cloud.conf");

    ret = appconfig_load(&cloud_config, filename, 1, NULL);
    if(!ret && !silent) {
        info("CONFIG: cannot load cloud config '%s'. Running with internal defaults.", filename);
    }
    freez(filename);
}
