#include <assert.h>
#include <tfe_cmsg.h>
#include <tfe_utils.h>
#include <tfe_stream.h>
#include <tfe_resource.h>
#include <cjson/cJSON.h>
#include <MESA/maat.h>

#include "chaining_policy.h"

enum traffic_type
{
    TRAFFIC_TYPE_NONE = 0,
    TRAFFIC_TYPE_RAW = 1,
    TRAFFIC_TYPE_DECRYPTED = 2,
};

struct chaining_param
{
    uint64_t rule_id;
    int ref_cnt;
    enum traffic_type type;
};

struct chaining_policy_enforcer
{
    struct maat *maat;
    int table_id;
    void *logger;
};

static void chaining_param_new_cb(const char *table_name, int table_id, const char *key, const char *table_line, void **ad, long argl, void *argp)
{
    cJSON *json = NULL;
    cJSON *item = NULL;
    cJSON *element = NULL;
    size_t user_region_offset = 0;
    size_t user_region_len = 0;
    struct chaining_param *param = NULL;
    struct chaining_policy_enforcer *enforcer = (struct chaining_policy_enforcer *)argp;

    if (maat_helper_read_column(table_line, 7, &user_region_offset, &user_region_len) < 0)
    {
        TFE_LOG_ERROR(enforcer->logger, "unexpected chaining rule: (invalid user region) %s", table_line);
        return;
    }

    char *json_str = (char *)calloc(user_region_len + 1, sizeof(char));
    memcpy(json_str, table_line + user_region_offset, user_region_len);
    json = cJSON_Parse(json_str);
    if (json == NULL)
    {
        TFE_LOG_ERROR(enforcer->logger, "unexpected chaining rule: (invalid json format) %s", table_line);
        goto error_out;
    }

    param = (struct chaining_param *)calloc(1, sizeof(struct chaining_param));
    param->rule_id = atoll(key);
    param->ref_cnt = 1;

    item = cJSON_GetObjectItem(json, "targeted_traffic");
    if (!item || !cJSON_IsString(item))
    {
        TFE_LOG_ERROR(enforcer->logger, "unexpected chaining rule: (invalid targeted_traffic param) %s", table_line);
        goto error_out;
    }
    if (strcasecmp(item->valuestring, "raw") == 0)
    {
        param->type = TRAFFIC_TYPE_RAW;
    }
    else if (strcasecmp(item->valuestring, "decrypted") == 0)
    {
        param->type = TRAFFIC_TYPE_DECRYPTED;
    }
    else
    {
        TFE_LOG_ERROR(enforcer->logger, "unexpected chaining rule: (invalid targeted_traffic param) %s", table_line);
        goto error_out;
    }

    *ad = param;
    TFE_LOG_INFO(enforcer->logger, "Add chaining rule: %lu", param->rule_id);

    cJSON_Delete(json);
    free(json_str);
    return;

error_out:
    if (json)
    {
        cJSON_Delete(json);
        json = NULL;
    }

    if (json_str)
    {
        free(json_str);
        json_str = NULL;
    }

    if (param)
    {
        free(param);
        param = NULL;
    }
}

static void chaining_param_free_cb(int table_id, void **ad, long argl, void *argp)
{
    struct chaining_policy_enforcer *enforcer = (struct chaining_policy_enforcer *)argp;
    struct chaining_param *param = (struct chaining_param *)*ad;
    if (param == NULL)
    {
        return;
    }

    if ((__sync_sub_and_fetch(&param->ref_cnt, 1) == 0))
    {
        TFE_LOG_INFO(enforcer->logger, "Del chaining policy %lu", param->rule_id);
        free(param);
        *ad = NULL;
    }
}

static void chaining_param_free(struct chaining_param *param)
{
    chaining_param_free_cb(0, (void **)&param, 0, NULL);
}

static void chaining_param_dup_cb(int table_id, void **to, void **from, long argl, void *argp)
{
    struct chaining_param *param = (struct chaining_param *)*from;
    if (param)
    {
        __sync_add_and_fetch(&(param->ref_cnt), 1);
        *to = param;
    }
    else
    {
        *to = NULL;
    }
}

struct chaining_policy_enforcer *chaining_policy_enforcer_create(void *logger)
{
    int ret = 0;
    struct chaining_policy_enforcer *enforcer = ALLOC(struct chaining_policy_enforcer, 1);
    enforcer->maat = (struct maat *)tfe_bussiness_resouce_get(STATIC_MAAT);
    enforcer->logger = logger;
    enforcer->table_id = maat_get_table_id(enforcer->maat, "SERVICE_CHAINING_COMPILE");
    if (enforcer->table_id < 0)
    {
        TFE_LOG_ERROR(enforcer->logger, "failed at register table of SERVICE_CHAINING_COMPILE, ret = %d", enforcer->table_id);
        goto error_out;
    }

    ret = maat_plugin_table_ex_schema_register(enforcer->maat, "SERVICE_CHAINING_COMPILE",
                                               chaining_param_new_cb,
                                               chaining_param_free_cb,
                                               chaining_param_dup_cb,
                                               0, enforcer);
    if (ret < 0)
    {
        TFE_LOG_ERROR(enforcer->logger, "failed at register callback of SERVICE_CHAINING_COMPILE, ret = %d", ret);
        goto error_out;
    }
    return enforcer;

error_out:
    chaining_policy_enforcer_destory(enforcer);
    return NULL;
}

void chaining_policy_enforcer_destory(struct chaining_policy_enforcer *enforcer)
{
    if (enforcer)
    {
        free(enforcer);
        enforcer = NULL;
    }
}

void chaining_policy_enforce(struct chaining_policy_enforcer *enforcer, struct tfe_cmsg *cmsg, uint64_t rule_id)
{
    uint16_t size = 0;
    char rule_id_str[16] = {0};
    uint8_t enalbe_decrypted_traffic_steering = 0;

    tfe_cmsg_get_value(cmsg, TFE_CMSG_TCP_DECRYPTED_TRAFFIC_STEERING, (unsigned char *)&enalbe_decrypted_traffic_steering, sizeof(enalbe_decrypted_traffic_steering), &size);
    if (enalbe_decrypted_traffic_steering == 1)
    {
        return;
    }

    snprintf(rule_id_str, sizeof(rule_id_str), "%lu", rule_id);
    struct chaining_param *param = (struct chaining_param *)maat_plugin_table_get_ex_data(enforcer->maat, enforcer->table_id, rule_id_str);
    if (param == NULL)
    {
        TFE_LOG_INFO(enforcer->logger, "Failed to get chaining parameter of policy %lu.", rule_id);
        return;
    }

    if (param->type == TRAFFIC_TYPE_DECRYPTED)
    {
        enalbe_decrypted_traffic_steering = 1;
    }

    tfe_cmsg_set(cmsg, TFE_CMSG_TCP_DECRYPTED_TRAFFIC_STEERING, (unsigned char *)&enalbe_decrypted_traffic_steering, sizeof(enalbe_decrypted_traffic_steering));
    chaining_param_free(param);
}