/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL). See the LICENSE file in the root
 * directory for license terms. You may choose either license, or both.
 */

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

#include "iotcs.h"
#include "iotcs/iotcs_private.h"
#include "util/util_buffer.h"
#include "util/util_thread_private.h"
#include "util/util.h"
#include "trusted_assets_manager/iotcs_tam.h"
#include "iotcs_port_crypto.h"
#include "policy/device_policy.h"

#include "log/log.h"
#define IOTCSP_MODULE_LOG_CHANNEL LOG_CHANNEL_CORE
#include "log/log_template.h"

static char* iotcsp_generate_indirect_activation_request_body(iotcs_bool restricted, const char *urns[],
        const iotcs_key_value *pmetadata, const char *hardware_id) {
    int cx = 0;
    int dm_cx = 0;
    int i = 0;
    const int BODY_SIZE = UTIL_INDIRECT_ACTIVATION_BODY_LENGTH + UTIL_INDIRECT_ACTIVATION_DM_STR_LENGTH;
    char* device_models_str = util_get_indirect_activation_device_models_buffer();
    char* signature_str = util_get_indirect_activation_signature_string_buffer();
    char* signature = util_get_indirect_activation_signature_buffer();
    size_t signature_str_length;
    size_t signature_length = UTIL_ACTIVATION_SIGNATURE_STRING_LENGTH;
    char* body = util_get_payload_buffer();

    signature_str[0] = '\0';

    // form a line of device models
    GOTO_ERR(0 > (dm_cx += util_safe_snprintf(device_models_str, UTIL_INDIRECT_ACTIVATION_DM_STR_LENGTH, "\"deviceModels\":[")));
    while (urns[i]) {
        GOTO_ERR(0 > (dm_cx += util_safe_snprintf(device_models_str + dm_cx, UTIL_INDIRECT_ACTIVATION_DM_STR_LENGTH - dm_cx, "\"%s\",", urns[i])));
        ++i;
    }
    // delete last comma in device models
    dm_cx -= 1;
    // add last bracket in device models
    GOTO_ERR(0 > util_safe_snprintf(device_models_str + dm_cx, UTIL_INDIRECT_ACTIVATION_DM_STR_LENGTH - dm_cx, "]"));

    // form request body
    // add first bracket
    GOTO_ERR(0 > (cx += util_safe_snprintf(body, BODY_SIZE, "{")));

    signature_str_length = tam_sign_with_shared_secret(tam_get_endpoint_id(), strlen(tam_get_endpoint_id()),
            "HmacSHA256", signature_str, UTIL_ACTIVATION_SIGNATURE_LENGTH, hardware_id);

    if (restricted && signature_str_length <= 0) {
        signature_str_length = tam_sign_with_private_key(tam_get_endpoint_id(), strlen(tam_get_endpoint_id()),
                "HmacSHA256", signature_str, UTIL_ACTIVATION_SIGNATURE_LENGTH);
    }

    // add signature
    if (signature_str_length > 0) {
        GOTO_ERR(IOTCS_RESULT_OK != iotcs_port_crypto_encode_base64((char*) signature, &signature_length,
                signature_str, signature_str_length));
        GOTO_ERR(0 > (cx += util_safe_snprintf(body + cx, BODY_SIZE - cx, "\"signature\":\"%.*s\",", signature_length, signature)));
    }
    // add hardware
    GOTO_ERR(0 > (cx += util_safe_snprintf(body + cx, BODY_SIZE - cx, "\"hardwareId\":\"%s\",", hardware_id)));
    // add device models
    GOTO_ERR(0 > (cx += util_safe_snprintf(body + cx, BODY_SIZE - cx, "%s,", device_models_str)));

    while (pmetadata->key) {
        GOTO_ERR(0 > (cx += util_safe_snprintf(body + cx, BODY_SIZE - cx, "\"%s\":\"%s\",",
                pmetadata->key, pmetadata->value)));
        ++pmetadata;
    }
    // delete last comma
    cx -= 1;
    // add last bracket
    GOTO_ERR(0 > (util_safe_snprintf(body + cx, BODY_SIZE - cx, "}")));

    return body;

error:
    return NULL;
}

iotcs_result iotcs_register_device(iotcs_bool restricted, const char *hardware_id, const iotcs_key_value metadata[],
        const char *urns[], char *endpoint_id) {
    const iotcs_key_value *pmetadata = metadata;
    protocol_response response;
    iotcs_result result = IOTCS_RESULT_FAIL;

    if (!pmetadata || !urns || !urns[0] || !hardware_id) {
        return IOTCS_RESULT_INVALID_ARGUMENT;
    }

    UTIL_LOCK_LL();

    protocol_request request = {
#ifdef IOTCS_USE_DRAFT_DEVICE_MODELS
        .url = "/iot/api/v2/activation/indirect/device?createDraft=true",
#else
        .url = "/iot/api/v2/activation/indirect/device?createDraft=false",
#endif
        .method = PROTOCOL_REQUEST_METHOD_POST,
        .headers =
        {
            .connection = "close",
            .accept = "application/json",
            .content_type = "application/json",
            .host = tam_get_server_host(),
            .x_endpointId = tam_get_endpoint_id()
        }
    };

    GOTO_ERR(NULL == (request.body = iotcsp_generate_indirect_activation_request_body(restricted, urns, pmetadata, hardware_id)));
    result = cl_proceed_request(request, &response);

    /*
     If not authorize then try to get token again.
     */
    if (result == IOTCS_RESULT_CANNOT_AUTHORIZE) {
        GOTO_ERR(IOTCS_RESULT_OK != cl_get_token());
        GOTO_ERR(NULL == (request.body = iotcsp_generate_indirect_activation_request_body(restricted, urns, pmetadata, hardware_id)));
        result = cl_proceed_request(request, &response);
    }

    GOTO_ERR(result != IOTCS_RESULT_OK);

    if (response.status_code == PROTOCOL_RESPONSE_CODE_ACCEPTED || response.status_code == PROTOCOL_RESPONSE_CODE_OK || response.status_code == PROTOCOL_RESPONSE_CODE_CREATED) {
        char* value = NULL;
        int output_length;
        json_tokens_t tokens_t;

        if (json_parse(response.body, strlen(response.body), &tokens_t) == IOTCS_RESULT_OK) {
            if (IOTCS_RESULT_OK == (result = json_get_string_value_by_key(&tokens_t, "endpointId", &value,
                    &output_length))) {
                memcpy(endpoint_id, value, output_length * sizeof (char));
                endpoint_id[output_length] = '\0';
            }
        } else {
            LOG_ERRS("json_parse_custom method failed");
            result = IOTCS_RESULT_FAIL;
        }
    } else {
        result = IOTCS_RESULT_FAIL;
    }

    UTIL_UNLOCK_LL();
#ifdef IOTCS_MESSAGE_DISPATCHER
#ifdef IOTCS_IMPLICIT_EDGE_COMPUTING
    int i;
    for (i = 0; urns[i]; i++) {
        dm_get_policy(endpoint_id, urns[i]);
    }
#endif
#endif
    return result;

error:
    UTIL_UNLOCK_LL();
    return result;
}
