#include <MESA/MESA_prof_load.h>
#include <MESA/Maat_rule.h>
#include <cjson/cJSON.h>
#include <tfe_kafka_logger.h>
#include <tfe_proxy.h>
#include <tfe_resource.h>

#define MAAT_INPUT_JSON 0
#define MAAT_INPUT_REDIS 1
#define MAAT_INPUT_FILE 2

struct maat_table_info
{
    int id;
    const char *name;
};

static Maat_feather_t static_maat = NULL;
static tfe_kafka_logger_t *kafka_logger = NULL;
static char *device_id = NULL;
static char *effective_device_tag=NULL;

static Maat_feather_t create_maat_feather(const char *instance_name, const char *profile, const char *section, int max_thread, void *logger)
{
    Maat_feather_t target;
    int input_mode = 0, maat_stat_on = 0, maat_perf_on = 0;
    int ret = 0, scan_detail = 0, effect_interval = 60;
    char table_info[TFE_STRING_MAX] = {0}, inc_cfg_dir[TFE_STRING_MAX] = {0}, ful_cfg_dir[TFE_STRING_MAX] = {0};
    char redis_server[TFE_STRING_MAX] = {0};
    char redis_port_range[TFE_STRING_MAX] = {0};
    char accept_tags[TFE_STRING_MAX] = {0};
    char accept_path[TFE_PATH_MAX] = {0};
    int redis_port_begin = 0, redis_port_end = 0;
    int redis_port_select = 0;
    int redis_db_idx = 0;
    int deferred_load_on = 0;
    char json_cfg_file[TFE_STRING_MAX] = {0}, maat_stat_file[TFE_STRING_MAX] = {0};

    MESA_load_profile_int_def(profile, section, "maat_input_mode", &(input_mode), 0);
    MESA_load_profile_int_def(profile, section, "stat_switch", &(maat_stat_on), 1);
    MESA_load_profile_int_def(profile, section, "perf_switch", &(maat_perf_on), 1);
    MESA_load_profile_string_def(profile, section, "table_info", table_info, sizeof(table_info), "");
    MESA_load_profile_string_def(profile, section, "accept_path", accept_path, sizeof(accept_path), "");
    MESA_load_profile_string_def(profile, section, "json_cfg_file", json_cfg_file, sizeof(json_cfg_file), "");
    MESA_load_profile_string_def(profile, section, "maat_redis_server", redis_server, sizeof(redis_server), "");
    MESA_load_profile_string_def(profile, section, "maat_redis_port_range", redis_port_range, sizeof(redis_server), "6379");
    MESA_load_profile_int_def(profile, section, "maat_redis_db_index", &(redis_db_idx), 0);
    MESA_load_profile_string_def(profile, section, "inc_cfg_dir", inc_cfg_dir, sizeof(inc_cfg_dir), "");
    MESA_load_profile_string_def(profile, section, "full_cfg_dir", ful_cfg_dir, sizeof(ful_cfg_dir), "");
    MESA_load_profile_string_def(profile, section, "stat_file", maat_stat_file, sizeof(maat_stat_file), "");
    MESA_load_profile_int_def(profile, section, "effect_interval_s", &(effect_interval), 60);
    MESA_load_profile_int_def(profile, section, "deferred_load_on", &(deferred_load_on), 0);

    effect_interval *= 1000; //convert s to ms

    target = Maat_feather(max_thread, table_info, logger);
    Maat_set_feather_opt(target, MAAT_OPT_INSTANCE_NAME, instance_name, strlen(instance_name) + 1);
    switch (input_mode)
    {
    case MAAT_INPUT_JSON:
        if (!strlen(json_cfg_file))
        {
            TFE_LOG_ERROR(logger, "Invalid json_cfg_file, MAAT init failed.");
            goto error_out;
        }

        Maat_set_feather_opt(target, MAAT_OPT_JSON_FILE_PATH, json_cfg_file, strlen(json_cfg_file) + 1);
        break;
    case MAAT_INPUT_REDIS:
        if (!strlen(redis_server))
        {
            TFE_LOG_ERROR(logger, "Invalid maat_redis_server, MAAT init failed.");
            goto error_out;
        }

        ret = sscanf(redis_port_range, "%d-%d", &redis_port_begin, &redis_port_end);
        if (ret == 1)
        {
            redis_port_select = redis_port_begin;
        }
        else if (ret == 2)
        {
            srand(time(NULL));
            redis_port_select = redis_port_begin + rand() % (redis_port_end - redis_port_begin);
        }
        else
        {
            TFE_LOG_ERROR(logger, "Invalid redis port range %s, MAAT init failed.", redis_port_range);

            goto error_out;
        }

        Maat_set_feather_opt(target, MAAT_OPT_REDIS_IP, redis_server, strlen(redis_server) + 1);
        Maat_set_feather_opt(target, MAAT_OPT_REDIS_PORT, &redis_port_select, sizeof(redis_port_select));
        Maat_set_feather_opt(target, MAAT_OPT_REDIS_INDEX, &redis_db_idx, sizeof(redis_db_idx));
        break;
    case MAAT_INPUT_FILE:
        if (!strlen(ful_cfg_dir))
        {
            TFE_LOG_ERROR(logger, "Invalid ful_cfg_dir, MAAT init failed.");
            goto error_out;
        }

        if (!strlen(inc_cfg_dir))
        {
            TFE_LOG_ERROR(logger, "Invalid inc_cfg_dir, MAAT init failed.");
            goto error_out;
        }

        Maat_set_feather_opt(target, MAAT_OPT_FULL_CFG_DIR, ful_cfg_dir, strlen(ful_cfg_dir) + 1);
        Maat_set_feather_opt(target, MAAT_OPT_INC_CFG_DIR, inc_cfg_dir, strlen(inc_cfg_dir) + 1);
        break;
    default:
        TFE_LOG_ERROR(logger, "Invalid MAAT Input Mode: %d.", input_mode);
        goto error_out;
        break;
    }

    Maat_set_feather_opt(target, MAAT_OPT_FOREIGN_CONT_DIR, "./pangu_files", strlen("./pangu_files") + 1);
    if (maat_stat_on)
    {
        Maat_set_feather_opt(target, MAAT_OPT_STAT_FILE_PATH, maat_stat_file, strlen(maat_stat_file) + 1);
        Maat_set_feather_opt(target, MAAT_OPT_STAT_ON, NULL, 0);
        if (maat_perf_on)
        {
            Maat_set_feather_opt(target, MAAT_OPT_PERF_ON, NULL, 0);
        }
    }

    if (deferred_load_on)
    {
        Maat_set_feather_opt(target, MAAT_OPT_DEFERRED_LOAD, &deferred_load_on, sizeof(deferred_load_on));
    }
    Maat_set_feather_opt(target, MAAT_OPT_EFFECT_INVERVAL_MS, &effect_interval, sizeof(effect_interval));
    Maat_set_feather_opt(target, MAAT_OPT_SCAN_DETAIL, &scan_detail, sizeof(scan_detail));
    if (strlen(accept_path) > 0)
    {
        MESA_load_profile_string_def(accept_path, "maat", "ACCEPT_TAGS", accept_tags, sizeof(accept_tags), "{\"tags\":[{\"tag\":\"device_id\",\"value\":\"device_1\"}]}");
        Maat_set_feather_opt(target, MAAT_OPT_ACCEPT_TAGS, &accept_tags, sizeof(accept_tags));
        TFE_LOG_INFO(logger, "tfe accept tags	  : %s", accept_tags);
    }

    ret = Maat_initiate_feather(target);
    if (ret < 0)
    {
        TFE_LOG_ERROR(logger, "%s MAAT init failed.", __FUNCTION__);
        goto error_out;
    }

    return target;
error_out:
    Maat_burn_feather(target);
    return NULL;
}

static tfe_kafka_logger_t *create_kafka_logger(const char *profile, const char *section, void *logger)
{
    int enable = 0;
    char nic_name[64] = {0};
    char brokerlist[TFE_STRING_MAX] = {0};
    char topic_name[TFE_STRING_MAX] = {0};
	char sasl_username[TFE_STRING_MAX] = {0};
	char sasl_passwd[TFE_STRING_MAX] = {0};
    tfe_kafka_logger_t *kafka_logger = NULL;

    MESA_load_profile_int_def(profile, section, "enable", &enable, 1);
    MESA_load_profile_string_def(profile, section, "NIC_NAME", nic_name, sizeof(nic_name), "eth0");
    MESA_load_profile_string_def(profile, section, "KAFKA_BROKERLIST", brokerlist, sizeof(brokerlist), "");
    MESA_load_profile_string_def(profile, section, "KAFKA_TOPIC", topic_name, sizeof(topic_name), "POLICY-EVENT-LOG");
	MESA_load_profile_string_def(profile, section, "SASL_USERNAME", sasl_username, sizeof(sasl_username), "");
	MESA_load_profile_string_def(profile, section, "SASL_PASSWD", sasl_passwd, sizeof(sasl_passwd), "");

    if (!strlen(brokerlist))
    {
        TFE_LOG_ERROR(logger, "tfe kafka init failed, no brokerlist in profile %s section %s.", profile, section);
        return NULL;
    }

    kafka_logger = tfe_kafka_logger_create(enable, nic_name, brokerlist, topic_name, sasl_username, sasl_passwd, logger);
    if (kafka_logger == NULL)
    {
        TFE_LOG_ERROR(logger, "tfe kafka init failed, error to create kafka logger.");
        return NULL;
    }

    TFE_LOG_INFO(logger, "tfe kafka logger     : %s", enable ? "ENABLE" : "DISABLE");
    TFE_LOG_INFO(logger, "tfe kafka topic      : %s", topic_name);
    TFE_LOG_INFO(logger, "tfe kafka brokerlist : %s", brokerlist);

	if (strlen(sasl_username) > 0 && strlen(sasl_passwd) > 0)
	{
    	TFE_LOG_INFO(logger, "tfe kafka sasl_username : %s", sasl_username);
    	TFE_LOG_INFO(logger, "tfe kafka sasl_passwd : %s", sasl_passwd);
	}

    return kafka_logger;
}

static char *cerate_device_id(const char *profile, const char *section, void *logger)
{
    int ret = -1;
    size_t device_id_size = 0;
    char *tsg_sn_file = NULL, *device_id;

    const char *device_def_id = "DFT2201925000001";
    cJSON *json = NULL, *item = NULL;
    char device_id_filepath[TFE_STRING_MAX] = {0};

    ret = MESA_load_profile_string_def(profile, section, "device_id_filepath", device_id_filepath, sizeof(device_id_filepath), NULL);
    if (ret < 0)
    {
        TFE_LOG_ERROR(logger, "Invalid device parameter: device_id_filepath not existed in profile %s section %s.", profile, section);
        goto finish;
    }
    tsg_sn_file = tfe_read_file(device_id_filepath, &device_id_size);
    if (tsg_sn_file == NULL)
    {
        TFE_LOG_ERROR(logger, "Invalid device parameter: device sn file not existed.");
        goto finish;
    }
    json = cJSON_Parse(tsg_sn_file);
    if (json == NULL)
    {
        TFE_LOG_ERROR(logger, "Invalid device parameter: %s invalid json format", tsg_sn_file);
        goto finish;
    }
    item = cJSON_GetObjectItem(json, "sn");
    if (unlikely(!item || !cJSON_IsString(item)))
    {
        TFE_LOG_ERROR(logger, "Invalid device parameter: %s invalid json format", tsg_sn_file);
        goto finish;
    }
    device_id = tfe_strdup(item->valuestring);

    cJSON_Delete(json);
    TFE_LOG_INFO(logger, "tfe device id        : %s", device_id);

    return device_id;
finish:
    TFE_LOG_INFO(logger, "tfe use default device id        : %s", device_def_id);
    if (json)
        cJSON_Delete(json);
    return (char *)device_def_id;
}

static char* create_effective_device_tag(const char *profile, const char *section, void *logger)
{
	char *effective_device_tag=NULL;
	char accept_path[TFE_PATH_MAX] = {0}, accept_tags[TFE_STRING_MAX] = {0};

	MESA_load_profile_string_def(profile, section, "accept_path", accept_path, sizeof(accept_path), "");
	if(strlen(accept_path) > 0)
    {
        MESA_load_profile_string_def(accept_path, "maat", "ACCEPT_TAGS", accept_tags, sizeof(accept_tags), "");
    }
	if(strlen(accept_tags) <= 0)
	{
		return NULL;
	}
	effective_device_tag = tfe_strdup(accept_tags);
	TFE_LOG_INFO(logger, "tfe device tag       : %s", effective_device_tag);

	return effective_device_tag;
}

static struct maat_table_info maat_pub_tables[TABLE_TYPE_MAX] = {
    {0, "TSG_SECURITY_SOURCE_ASN"},
    {0, "TSG_SECURITY_DESTINATION_ASN"},
    {0, "TSG_SECURITY_SOURCE_LOCATION"},
    {0, "TSG_SECURITY_DESTINATION_LOCATION"},
    {0, "TSG_OBJ_SUBSCRIBER_ID"}};

static int register_maat_table()
{
    for (int i = 0; i < TABLE_TYPE_MAX; i++)
    {
        maat_pub_tables[i].id = Maat_table_register(static_maat, maat_pub_tables[i].name);
        if (maat_pub_tables[i].id < 0)
        {
            TFE_LOG_ERROR(g_default_logger, "Maat table %s register failed.", maat_pub_tables[i].name);
            return -1;
        }
    }

    return 0;
}

int tfe_bussiness_resouce_init()
{
    const char *profile_path = "./conf/tfe/tfe.conf";
    unsigned int thread_num = tfe_proxy_get_work_thread_count();
    static_maat = create_maat_feather("static", profile_path, "MAAT", thread_num, g_default_logger);
    if (!static_maat)
    {
        return -1;
    }

    kafka_logger = create_kafka_logger(profile_path, "kafka", g_default_logger);
    if (!kafka_logger)
    {
        return -1;
    }

    device_id = cerate_device_id(profile_path, "kafka", g_default_logger);

	effective_device_tag = create_effective_device_tag(profile_path, "MAAT", g_default_logger);

    if (register_maat_table())
    {
        return -1;
    }

    return 0;
}

void *tfe_bussiness_resouce_get(enum RESOURCE_TYPE type)
{
    switch (type)
    {
    case STATIC_MAAT:
        return static_maat;
    case KAFKA_LOGGER:
        return kafka_logger;
    case DEVICE_ID:
        return device_id;
	case EFFECTIVE_DEVICE_TAG:
		return effective_device_tag;
    default:
        return NULL;
    }
}

int tfe_bussiness_tableid_get(enum TABLE_TYPE type)
{
    return maat_pub_tables[type].id;
}