#include "ausf_app.hpp"

#include <boost/uuid/random_generator.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <cstdlib>
#include <iostream>
#include <stdexcept>

#include "itti.hpp"
#include "curl.hpp"
#include "logger.hpp"
#include "3gpp_29.510.h"
#include <nlohmann/json.hpp>
#include <stdexcept>

#include <curl/curl.h>
#include <pistache/http.h>
#include <pistache/mime.h>
extern "C" {
#include "dynamic_memory_check.h"
}
using namespace Pistache::Http;
using namespace Pistache::Http::Mime;
using namespace ausf_application;
using namespace config;
using namespace std;
extern itti_mw *itti_inst;
extern ausf_config ausf_cfg;
extern ausf_app *ausf_app_inst;

static std::size_t callback(const char* in, std::size_t size, std::size_t num, std::string* out){
    const std::size_t totalBytes(size * num);
    out->append(in, totalBytes);
    return totalBytes;
}
void ausf_app_task(void *);

void ausf_app_task(void*){
    const task_id_t task_id = TASK_AUSF_APP;
    itti_inst->notify_task_ready(task_id);
    do
    {
        std::shared_ptr<itti_msg> shared_msg = itti_inst->receive_msg(task_id);
        auto *msg = shared_msg.get();
        timer_id_t tid;
        switch (msg->msg_type)
        {
        case SBI_REGISTER_NF_INSTANCE_RESPONSE:
            Logger::ausf_app().debug("handle register response");
            if (itti_sbi_register_nf_instance_response* m =
                dynamic_cast<itti_sbi_register_nf_instance_response*>(msg)) {
                ausf_app_inst->handle_itti_msg(std::ref(*m));
            }
        break;
        case SBI_UPDATE_NF_INSTANCE_REQUEST:
            Logger::ausf_app().debug("send update request");
            ausf_app_inst->update_nf_instance(std::static_pointer_cast<itti_sbi_update_nf_instance_request>(shared_msg));
        break;
        case TIME_OUT:
            if (itti_msg_timeout *to = dynamic_cast<itti_msg_timeout *>(msg))
            {
                switch (to->arg1_user)
                {
                case TASK_AUSF_APP_PERIODIC_GETINFO:
                    tid = itti_inst->timer_setup(
                        20, 0, TASK_AUSF_APP,
                        TASK_AUSF_APP_PERIODIC_GETINFO, 0);
                    //TODO: periodic_getinfo_handler
                    break;
                case TASK_AUSF_APP_TIMEOUT_NRF_HEARTBEAT:
                    ausf_app_inst->timer_nrf_heartbeat_timeout(
                        to->timer_id, to->arg2_user);
                    break;
                default:
                    Logger::ausf_app().info(
                        "No handler for timer(%d) with arg1_user(%d) ", to->timer_id,
                        to->arg1_user);
                }
            }
            break;
        default:
            Logger::ausf_app().info("no handler for msg type %d", msg->msg_type);
        }
  } while (true);
}
ausf_app::ausf_app()
{
    Logger::ausf_app().startup("Creating AUSF application functionality layer");
    if(itti_inst->create_task(TASK_AUSF_APP,ausf_app_task,nullptr)){
        Logger::ausf_app().error("Cannot create task TASK_AUSF_APP");
        throw std::runtime_error("Cannot create task TASK_AUSF_APP");
    }

    if(ausf_cfg.nf_registion.compare("yes")==0){
        unsigned int microsecond = 10000;  // 10ms
        usleep(microsecond);
        register_to_nrf();

        timer_nrf_heartbeat = itti_inst->timer_setup(1, 0, TASK_AUSF_APP, TASK_AUSF_APP_TIMEOUT_NRF_HEARTBEAT, 0);

        timer_id_t tid = itti_inst->timer_setup(20, 0, TASK_AUSF_APP, TASK_AUSF_APP_PERIODIC_GETINFO, 0);
    }
    Logger::ausf_app().startup("Started");
}
void ausf_app::register_to_nrf(){
  // Create a NF profile to this instance
  generate_ausf_profile();
  // Send request to N11 to send NF registration to NRF
  trigger_nf_registration_request();
}

void ausf_app::generate_ausf_profile(){
    generate_uuid();
    nf_instance_profile.set_nf_instance_id(ausf_instance_id);
    nf_instance_profile.set_nf_instance_name("bupt-ausf");
    nf_instance_profile.set_nf_type("AUSF");
    nf_instance_profile.set_nf_status("REGISTERED");
    nf_instance_profile.set_nf_heartBeat_timer(1);
    nf_instance_profile.set_nf_priority(1);
    nf_instance_profile.set_nf_capacity(100);
    nf_instance_profile.add_nf_ipv4_addresses(ausf_cfg.sbi.addr4);

    nf_service_t nf_service        = {};
    nf_service.service_instance_id = "nausf_communication";
    nf_service.service_name        = "nausf_communication"; //TODO:to be updated
    nf_service_version_t version   = {};
    version.api_version_in_uri     = "v1";
    version.api_full_version       = "1.0.0";  // TODO: to be updated
    nf_service.versions.push_back(version);
    nf_service.scheme            = "http";
    nf_service.nf_service_status = "REGISTERED";

    ip_endpoint_t endpoint = {};
    endpoint.ipv4_address  = ausf_cfg.sbi.addr4;
    endpoint.transport     = "TCP";
    endpoint.port          = ausf_cfg.sbi.port;
    nf_service.ip_endpoints.push_back(endpoint);

    nf_instance_profile.add_nf_service(nf_service);
    // Display the profile
    nf_instance_profile.display();
}
void ausf_app::trigger_nf_registration_request(){
    Logger::ausf_app().debug("Send ITTI msg to N11 task to trigger the registration request to NRF");

    std::shared_ptr<itti_sbi_register_nf_instance_request> itti_msg =
            std::make_shared<itti_sbi_register_nf_instance_request>(TASK_AUSF_APP,TASK_AUSF_APP);
    itti_msg->profile=nf_instance_profile;
    register_nf_instance(itti_msg);
}

void ausf_app::timer_nrf_heartbeat_timeout(timer_id_t timer_id, uint64_t arg2_user){
  Logger::ausf_app().debug("Send ITTI msg to N11 task to trigger NRF Heartbeat");
  std::shared_ptr<itti_sbi_update_nf_instance_request> itti_msg =
      std::make_shared<itti_sbi_update_nf_instance_request>(
          TASK_AUSF_APP, TASK_AUSF_APP);

  oai::ausf::model::PatchItem patch_item = {};
  patch_item.setOp("replace");
  patch_item.setPath("/nfStatus");
  patch_item.setValue("REGISTERED");
  itti_msg->patch_items.push_back(patch_item);
  itti_msg->ausf_instance_id = ausf_instance_id;

  int ret = itti_inst->send_msg(itti_msg);
  if (0 != ret) {
    Logger::ausf_app().error(
        "Could not send ITTI message %s to task TASK_AUSF_APP",
        itti_msg->get_msg_name());
  } else {
    Logger::ausf_app().debug(
        "Set a timer to the next Heart-beat (%d)",
        nf_instance_profile.get_nf_heartBeat_timer());
    timer_nrf_heartbeat = itti_inst->timer_setup(
        nf_instance_profile.get_nf_heartBeat_timer(), 0, TASK_AUSF_APP,
        TASK_AUSF_APP_TIMEOUT_NRF_HEARTBEAT,
        0);  // TODO arg2_user
  }
}
void ausf_app::generate_uuid(){
    ausf_instance_id = to_string(boost::uuids::random_generator()());
}
void ausf_app::register_nf_instance(std::shared_ptr<itti_sbi_register_nf_instance_request> msg)
{
    Logger::ausf_app().debug("Send NF Instance Registration to NRF (HTTP version %d)",
      msg->http_version);
    nlohmann::json json_data = {};
    msg->profile.to_json(json_data);

    std::string url =
      std::string(inet_ntoa(*((struct in_addr*) &ausf_cfg.nnrf.addr4))) +
      ":" + std::to_string(ausf_cfg.nnrf.port) + "/nnrf-nfm/" +
      ausf_cfg.nnrf.api_version + "/nf-instances/" +
      msg->profile.get_nf_instance_id();
    Logger::ausf_app().debug(
        "Send NF Instance Registration to NRF, NRF URL %s", url.c_str());
    std::string body = json_data.dump();
    Logger::ausf_app().debug("Send NF Instance Registration to NRF, msg body:\n % s ", body.c_str());
    curl_global_init(CURL_GLOBAL_ALL);
    CURL* curl = curl = curl_easy_init();

  if (curl) {
    CURLcode res               = {};
    struct curl_slist* headers = nullptr;
    // headers = curl_slist_append(headers, "charsets: utf-8");
    headers = curl_slist_append(headers, "content-type: application/json");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
    curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 100L);

    if (msg->http_version == 2) {
      curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
      // we use a self-signed test server, skip verification during debugging
      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
      curl_easy_setopt(
          curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
    }

    // Response information.
    long httpCode = {0};
    std::unique_ptr<std::string> httpData(new std::string());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, httpData.get());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.length());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
    res = curl_easy_perform(curl);
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);

    Logger::ausf_app().debug(
        "NF Instance Registration, response from NRF, HTTP Code: %d", httpCode);

    if (httpCode == 201) {
      nlohmann::json response_data = {};
      try {
        response_data = nlohmann::json::parse(*httpData.get());
      } catch (nlohmann::json::exception& e) {
        Logger::ausf_app().warn(
            "NF Instance Registration, could not parse json from the NRF "
            "response");
      }
      Logger::ausf_app().debug(
          "NF Instance Registration, response from NRF, json data: \n %s",
          response_data.dump().c_str());

      // send response to APP to process
      std::shared_ptr<itti_sbi_register_nf_instance_response> itti_msg =
          std::make_shared<itti_sbi_register_nf_instance_response>(
              TASK_AUSF_APP, TASK_AUSF_APP);
      itti_msg->http_response_code = httpCode;
      itti_msg->http_version       = msg->http_version;
      Logger::ausf_app().debug("Registered AUSF profile (from NRF)");
      itti_msg->profile.from_json(response_data);

      int ret = itti_inst->send_msg(itti_msg);
      if (0 != ret) {
        Logger::ausf_app().error(
            "Could not send ITTI message %s to task TASK_SMF_APP",
            itti_msg->get_msg_name());
      }
    } else {
      Logger::ausf_app().warn(
          "NF Instance Registration, could not get response from NRF");
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
  }
  curl_global_cleanup();
}
void ausf_app::handle_itti_msg(itti_sbi_register_nf_instance_response& r) {
  Logger::ausf_app().debug("Handle NF Instance Registration response");

  //nf_instance_profile = r.profile;
  // Set heartbeat timer
  Logger::ausf_app().debug(
      "Set value of NRF Heartbeat timer to %d",
      r.profile.get_nf_heartBeat_timer());
  /*timer_nrf_heartbeat = itti_inst->timer_setup(
      r.profile.get_nf_heartBeat_timer(), 0, TASK_AUSF_APP,
      TASK_AUSF_APP_TIMEOUT_NRF_HEARTBEAT,
      0);*/  // TODO arg2_user
}
void ausf_app::update_nf_instance(
  std::shared_ptr<itti_sbi_update_nf_instance_request> msg) {
  Logger::ausf_app().debug(
      "Send NF Update to NRF (HTTP version %d)", msg->http_version);

  nlohmann::json json_data = nlohmann::json::array();
  for (auto i : msg->patch_items) {
    nlohmann::json item = {};
    to_json(item, i);
    json_data.push_back(item);
  }
  std::string body = json_data.dump();
  Logger::ausf_app().debug("Send NF Update to NRF, Msg body %s", body.c_str());

  std::string url =
      std::string(inet_ntoa(*((struct in_addr*) &ausf_cfg.nnrf.addr4))) +
      ":" + std::to_string(ausf_cfg.nnrf.port) + "/nnrf-nfm/" +
      ausf_cfg.nnrf.api_version + "/nf-instances/" +
      msg->ausf_instance_id;

  Logger::ausf_app().debug("Send NF Update to NRF, NRF URL %s", url.c_str());

  curl_global_init(CURL_GLOBAL_ALL);
  CURL* curl = curl = curl_easy_init();

  if (curl) {
    CURLcode res               = {};
    struct curl_slist* headers = nullptr;
    // headers = curl_slist_append(headers, "charsets: utf-8");
    headers = curl_slist_append(
        headers, "content-type: application/json");  // TODO: json-patch+json
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
    curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 100L);

    if (msg->http_version == 2) {
      curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
      // we use a self-signed test server, skip verification during debugging
      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
      curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
      curl_easy_setopt(
          curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
    }

    // Response information.
    long httpCode = {0};
    std::unique_ptr<std::string> httpData(new std::string());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &callback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, httpData.get());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.length());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
    res = curl_easy_perform(curl);
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);

    Logger::ausf_app().debug(
        "NF Update, response from NRF, HTTP Code: %d", httpCode);

    if (httpCode==200||httpCode == 204) {
      Logger::ausf_app().debug("NF Update, got successful response from NRF");

      // TODO: in case of response containing NF profile
      // send response to APP to process
      std::shared_ptr<itti_sbi_update_nf_instance_response> itti_msg =
          std::make_shared<itti_sbi_update_nf_instance_response>(
              TASK_AUSF_APP, TASK_AUSF_APP);
      itti_msg->http_response_code = httpCode;
      itti_msg->http_version       = msg->http_version;
      itti_msg->ausf_instance_id    = msg->ausf_instance_id;

      /*int ret = itti_inst->send_msg(itti_msg);
      if (0 != ret) {
        Logger::ausf_app().error(
            "Could not send ITTI message %s to task TASK_AUSF_APP",
            itti_msg->get_msg_name());
      }*/
    } else {
      Logger::ausf_app().warn("NF Update, could not get response from NRF");
    }

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
  }
  curl_global_cleanup();
}
