/*
 * Copyright (C) 2020 MERA
 *
 * 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.
 */

#include <afb/afb-binding.h>
#include <json-c/json.h>

#include <memory>
#include <algorithm>

#include "utils.h"
#include "ClientManager.h"
#include "CloudType.h"
#include "AwsClient.h"
#include "AzureClient.h"

#include <glib.h>

namespace
{

std::map<std::string, std::unique_ptr<CloudClient>> g_clouds;

}


static bool loadConf()
{
    const char* CONF_FILE_PATH{"/etc/config.ini"};
    const char* DEFAULT_ETC_PATH{"/etc/cloudproxy-service"};
    const char *p = getenv("AFM_APP_INSTALL_DIR");
    if(!p)
    {
        AFB_ERROR("AFM_APP_INSTALL_DIR is not set, try to find conf in %s", DEFAULT_ETC_PATH);
        p = DEFAULT_ETC_PATH;
    }

    std::string conf_path = p;
    conf_path += CONF_FILE_PATH;

    g_autoptr(GKeyFile) conf_file = g_key_file_new();
    g_autoptr(GError) error = nullptr;

    AFB_DEBUG("Load config file: %s", conf_path.c_str());
    if (!conf_file || !g_key_file_load_from_file(conf_file, conf_path.c_str(), G_KEY_FILE_NONE, &error))
    {
        AFB_ERROR("Can't load file %s", conf_path.c_str());
        return false;
    }

    //-- Azure parameters:
    {
        std::unique_ptr<CloudClient> azure{new AzureClient};
        if (!azure->loadConf(conf_file))
            return false;

        if (azure->enabled())
            g_clouds[CloudType::Azure] = std::move(azure);
        else
            g_clouds[CloudType::Azure].reset();
    }

    //-- AWS parameters:
    {
        std::unique_ptr<CloudClient> aws{new AwsClient};
        if (!aws->loadConf(conf_file))
            return false;

        if (aws->enabled())
            g_clouds[CloudType::Aws] = std::move(aws);
        else
            g_clouds[CloudType::Aws].reset();
    }

    if (!std::any_of(g_clouds.begin(), g_clouds.end(), [](const auto& c){ return !!c.second; }))
    {
        AFB_ERROR("All cloud connection types are disabled by configuration");
        return false;
    }

    return true;
}


static void pingSample(afb_req_t request)
{
   static int pingcount = 0;
   afb_req_success_f(request, json_object_new_int(pingcount), "Ping count = %d", pingcount);
   AFB_NOTICE("Verbosity macro at level notice invoked at ping invocation count = %d", pingcount);
   pingcount++;
}


static void sendMessage(afb_req_t request)
{
    AFB_NOTICE("%s called", __FUNCTION__);

    json_object* object = afb_req_json(request);
    if (!object)
    {
        AFB_ERROR("Can't parse request");
        afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
        return;
    }

    const std::string appid{utils::get_application_id(request)};
    if (appid.empty())
    {
        AFB_ERROR("can't obtain application_id from request");
        afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
        return;
    }

    auto read_string = [object](const char* tag, std::string& read_to)
    {
        json_object* jobj{nullptr};
        if (!json_object_object_get_ex(object, tag, &jobj))
        {
            AFB_ERROR("can't obtain %s from request", tag);
            return false;
        }
        read_to = json_object_get_string(jobj);
        return true;
    };

    std::string cloud_type;
    if (!read_string("cloud_type", cloud_type))
    {
        afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
        return;
    }

    std::string data;
    if (!read_string("data", data))
    {
        afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
        return;
    }

    if (!CloudType::isSupported(cloud_type))
    {
        AFB_ERROR("Unsupported cloud type is requested: '%s'", cloud_type.c_str());
        afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
        return;
    }

    if (!g_clouds[cloud_type])
    {
        AFB_ERROR("%s cloud connection is disabled by config", cloud_type.c_str());
        afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
        return;
    }

    CloudClient* client{g_clouds[cloud_type.c_str()].get()};
    if (!client->connected() && !client->createConnection())
    {
        AFB_ERROR("Can't create connection to %s cloud", cloud_type.c_str());
        afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
        return;
    }

    if (!client->sendMessage(appid, data))
    {
        AFB_ERROR("Can't send message to %s cloud", cloud_type.c_str());
        afb_req_fail_f(request, "failed", "called %s", __FUNCTION__);
        return;
    }

    afb_req_success(request, json_object_new_object(), __FUNCTION__);
}


static void subscribe(afb_req_t request)
{
    AFB_NOTICE("%s called", __FUNCTION__);

    std::string req_appid{utils::get_application_id(request)};
    if(req_appid.empty())
    {
        AFB_ERROR("Can't subscribe: empty appid");
        afb_req_fail_f(request, "%s failed: application_id is not defined in request", __FUNCTION__);
        return;
    }

    if (!ClientManager::instance().handleRequest(request, __FUNCTION__, req_appid))
    {
        AFB_ERROR("%s failed in handleRequest", __FUNCTION__);
        afb_req_fail_f(request, "%s failed", __FUNCTION__);
    }
    else
    {
        afb_req_success(request, json_object_new_object(), __FUNCTION__);
    }
}

static void unsubscribe(afb_req_t request)
{
    AFB_NOTICE("%s called", __FUNCTION__);

    std::string req_appid{utils::get_application_id(request)};
    if(req_appid.empty())
    {
        AFB_ERROR("Can't unsubscribe: empty appid");
        afb_req_fail_f(request, "%s failed: application_id is not defined in request", __FUNCTION__);
        return;
    }

    if (!ClientManager::instance().handleRequest(request, __FUNCTION__, req_appid))
    {
        AFB_ERROR("%s failedin handleRequest", __FUNCTION__);
        afb_req_fail_f(request, "%s failed", __FUNCTION__);
    }
    else
    {
        afb_req_success(request, json_object_new_object(), __FUNCTION__);
    }
}

/*
 * array of the verbs exported to afb-daemon
 */
static const afb_verb_t verbs[]= {
    /* VERB'S NAME                 FUNCTION TO CALL                  */
    { .verb="ping",              .callback=pingSample             },
    { .verb="sendMessage",       .callback=sendMessage            },
    { .verb="subscribe",         .callback=subscribe              },
    { .verb="unsubscribe",       .callback=unsubscribe            },
    {nullptr } /* marker for end of the array */
};


static int preinit(afb_api_t api)
{
    AFB_NOTICE("binding preinit (was register)");

    if (!loadConf())
    {
        AFB_ERROR("Can't load configuration file or configuration is wrong");
        return -1;
    }

    return 0;
}


static int init(afb_api_t api)
{
    AFB_NOTICE("binding init");
    return 0;
}

const afb_binding_t afbBindingExport = {
    .api = "cloudproxy",
    .specification = nullptr,
    .info = nullptr,
    .verbs = verbs,
    .preinit = preinit,
    .init = init,
    .onevent = nullptr
};