/*
 * Copyright (C) 2018 - 2019 "IoT.bzh"
 * Author "Romain Forlot" <romain.forlot@iot.bzh>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *	 http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>

#include "influxdb.h"

void influxdb_write_curl_cb(void* closure, int status, CURL* curl, const char* result, size_t size)
{
    afb_req_t request = (afb_req_t)closure;
    long rep_code = curl_wrap_response_code_get(curl);
    switch (rep_code) {
    case 204:
        AFB_REQ_DEBUG(request, "Request correctly written");
        afb_req_success(request, NULL, "Request has been successfully written");
        break;
    case 400:
        afb_req_fail(request, "Bad request", result);
        break;
    case 401:
        afb_req_fail(request, "Unauthorized access", result);
        break;
    case 404:
        afb_req_fail(request, "Not found", result);
        AFB_REQ_NOTICE(request, "Attempt to create the DB '" DEFAULT_DB "'");
        create_database(request);
        break;
    case 500:
        afb_req_fail_f(request, "Timeout", "Overloaded server: %s", result);
        break;
    default:
        afb_req_fail_f(request, "Failure", "Unexpected behavior (code %ld).", rep_code);
        break;
    }
}

// query: destination buffer
// node: head node of the list
// list_sep: separator to append in front of the list
// item_sep: separator to append between items of the list
static void serialize_list_to_query(char* query, struct list* node, bool quoteString, const char* list_sep, const char* item_sep)
{
    bool first = true;

    while (node != NULL) {
        if (first) {
            concatenate(query, node->key, list_sep);
            first = false;
        } else {
            concatenate(query, node->key, item_sep);
        }

        if (json_object_is_type(node->value, json_type_string))
            concatenate_str(query, json_object_get_string(node->value), "=", quoteString);
        else
            concatenate(query, json_object_to_json_string(node->value), "=");
        node = node->next;
    }
}

static size_t format_write_args(char* query, struct series_t* serie)
{
    char* ts;

    strncat(query, serie->name, strlen(serie->name));

    serialize_list_to_query(query, serie->serie_columns.tags, false, ",", ",");
    serialize_list_to_query(query, serie->serie_columns.fields, true, " ", ",");

    if (asprintf(&ts, "%lu", serie->timestamp) > 0) {
        concatenate(query, ts, " ");
        free(ts);
        return strlen(query);
    }
    return -1;
}

CURL* make_curl_write_post(afb_api_t apiHandle, const char* url, json_object* metricsJ)
{
    CURL* curl = NULL;
    size_t lpd = 0, len_write = 0, i = 0;
    char** post_data;
    char* write = alloca(URL_MAXIMUM_LENGTH);   // FIXME: better to use malloc and relloc bigger when needed
    struct series_t* serie = NULL;
    json_object* metricsArrayJ = NULL;

    write[0] = '\0';

    if (json_object_is_type(metricsJ, json_type_array)) {
        lpd = json_object_array_length(metricsJ);
        metricsArrayJ = metricsJ;
    } else {
        metricsArrayJ = json_object_new_array();
        json_object_array_add(metricsArrayJ, metricsJ);
        lpd = 1;
    }

    serie = malloc(sizeof(struct series_t));
    post_data = calloc(lpd + 1, sizeof(void*));

    for (i = 0; i < lpd; i++) {
        memset(serie, 0, sizeof(struct series_t));

        if (unpack_metric_from_api(json_object_array_get_idx(metricsArrayJ, i), serie)) {
            AFB_API_ERROR(apiHandle, "ERROR unpacking metric. %s", json_object_to_json_string(metricsArrayJ));
            break;
        } else {
            if (!serie->name) {
                post_data[i] = NULL;
            } else {
                len_write = format_write_args(write, serie);
                if (len_write > 0) {
                    post_data[i] = malloc(len_write + 1);
                    strncpy(post_data[i], write, len_write+1);
                    write[0] = '\0';
                }
            }
        }
    }

    // Debugging purpose
    // for (i = 0; i < lpd; i++) {
    //     AFB_API_DEBUG(apiHandle, "curl POST '%s' '%s' ", url, post_data[i]);
    // }

    /* Check that we just do not broke the for loop before trying preparing CURL
	   request object */
    curl = i == lpd ? curl_wrap_prepare_post_unescaped(url, NULL, "\n", (const char* const*)post_data) : NULL;
    free(serie);
    for (i = 0; i < lpd; i++) {
        if (post_data[i]) {
            free(post_data[i]);
        }
    }
    free(post_data);

    return curl;
}

CURL* influxdb_write(afb_api_t apiHandle, const char* host, const char* port, json_object* metricJ)
{
    char url[URL_MAXIMUM_LENGTH]; /* Safe limit for most popular web browser */
    make_url_db(url, sizeof(url), host, port, "write", DEFAULT_DB);
    return make_curl_write_post(apiHandle, url, metricJ);
}

CTLP_CAPI(write_to_influxdb, source, argsJ, eventJ)
{
    afb_req_t request = source->request;
    const char* port = NULL;
    const char* host = NULL;
    CURL* curl_request;
    int rc = -1;

    json_object *req_args = afb_req_json(request),
                *portJ = NULL,
                *metric = NULL;

    if (wrap_json_unpack(req_args, "{s?s,s?o,so!}",
            "host", &host,
            "port", &portJ,
            "metric", &metric)
        || !metric) {
        afb_req_fail(request, "Failed", "Error processing arguments. Miss metric\
JSON object or malformed");
        rc = -1;
    } else {
        port = json_object_is_type(portJ, json_type_null) ? NULL : json_object_to_json_string(portJ);
        curl_request = influxdb_write(source->api, host, port, metric);
        curl_wrap_do(curl_request, influxdb_write_curl_cb, request);
        rc = 0;
    }

    return rc;
}