#include <netdb.h>
#include <unistd.h>
#include <cmath>
#include <uuid/uuid.h>

#include <config.h>
#include <helpers.h>
#include <models/controller_dbo.h>
#include <binn.h>
#include "api_v1_controllers.h"

using namespace api::v1;

enum DISCOVERY_MAPPING
{
    DISCOVERY_MAPPING_ID = 0,
    DISCOVERY_MAPPING_NAME = 1,
    DISCOVERY_MAPPING_COMMAND_PORT = 2,
    DISCOVERY_MAPPING_RELAY_COUNT = 3,
};

void controllers::post_discover(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{

    int discover_server_socket = helpers::bind_tcp_server("0.0.0.0", "0", config::discover_max_client_backlog);
    int discover_server_port = helpers::get_server_port(discover_server_socket);

    if(discover_server_port == -1)
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k500InternalServerError);
        callback(resp);
        return;
    }

    int16_t payload[1];
    payload[0] = discover_server_port;

    if(helpers::send_udp_broadcast("255.255.255.255", config::discover_port_dev, payload, sizeof(payload)) < 0)
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k500InternalServerError);
        callback(resp);
        return;
    }

    struct sockaddr_storage their_addr{};
    socklen_t addr_size;
    int client_fd, s_ret;
    fd_set accept_fds;
    struct timeval timeout{};

    uint8_t discover_answer_buf[1];

    controller_dbo **known_controllers = controller_dbo::get_all();

    while(true)
    {
        addr_size = sizeof(their_addr);

        FD_ZERO(&accept_fds);
        FD_SET(discover_server_socket, &accept_fds); // NOLINT(hicpp-signed-bitwise)

        timeout.tv_sec = config::discover_timeout_ms / 1000;
        timeout.tv_usec = (config::discover_timeout_ms % 1000) * 1000;

        s_ret = select(discover_server_socket + 1, &accept_fds, nullptr, nullptr, &timeout);
        if(s_ret == 0)
        {
            break;
        }
        else
        {
            if((client_fd = accept(discover_server_socket, (struct sockaddr *) &their_addr, &addr_size)) < 0)
            {
                LOG_ERROR << "Error Accepting client " << strerror(errno);
                continue;
            }

            size_t payload_length;

            if(recv(client_fd, &payload_length, sizeof(payload_length), 0) <= 0)
            {
                LOG_ERROR << "Error Receiving header from client";
                continue;
            }

            void *answer_payload = malloc((payload_length + 1));
            ssize_t bytes_transferred;

            if((bytes_transferred = recv(client_fd, answer_payload, payload_length, 0)) <= 0)
            {
                LOG_ERROR << "Error Receiving payload from client";
                continue;
            }

            struct sockaddr_in addr{};
            socklen_t client_addr_size = sizeof(struct sockaddr_in);
            if(getpeername(client_fd, (struct sockaddr *)&addr, &client_addr_size) != 0)
            {

                LOG_ERROR << "Error Receiving payload from client";
                continue;
            }

            uuid_t discovered_id;
            int id_size = sizeof(uuid_t);
            memcpy(&discovered_id, binn_map_blob(answer_payload, DISCOVERY_MAPPING_ID, &id_size), id_size);

            char *discovered_name = binn_map_str(answer_payload, DISCOVERY_MAPPING_NAME);
            uint32_t discovered_command_port = binn_map_uint32(answer_payload, DISCOVERY_MAPPING_COMMAND_PORT);
            uint8_t discovered_relay_count = binn_map_uint8(answer_payload, DISCOVERY_MAPPING_RELAY_COUNT);

            bool found_discovered_in_list = false;

            for(int i = 0; known_controllers[i] != nullptr; i++)
            {
                if(!found_discovered_in_list)
                {
                    if(uuid_compare(known_controllers[i]->id, discovered_id) == 0)
                    {
                        known_controllers[i]->active = true;
                        known_controllers[i]->update();
                        delete known_controllers[i];
                        found_discovered_in_list = true;
                        known_controllers[i] = known_controllers[i + 1];
                    }
                }
                else
                {
                    known_controllers[i] = known_controllers[i + 1];
                }
            }

            if(!found_discovered_in_list)
            {
                controller_dbo discovered_controller{};
                strcpy(discovered_controller.ip, inet_ntoa(addr.sin_addr));
                memcpy(discovered_controller.id, discovered_id, sizeof(uuid_t));
                strcpy(discovered_controller.name, discovered_name);
                discovered_controller.relay_count = discovered_relay_count;
                discovered_controller.port = discovered_command_port;
                discovered_controller.active = true;

                discovered_controller.insert();
            }
            free(answer_payload);

            discover_answer_buf[0] = config::discover_code_accept;
            send(client_fd, discover_answer_buf, sizeof(uint8_t), 0);
            close(client_fd);
        }
    }
    for(int i = 0; known_controllers[i] != nullptr; i++)
    {
        known_controllers[i]->active = false;
        known_controllers[i]->update();
        LOG_DEBUG << "Lost: " << known_controllers[i]->name;
    }
    controller_dbo::free_list(known_controllers);

    controller_dbo **all_controllers = controller_dbo::get_all();
    Json::Value all_controllers_json(Json::arrayValue);

    for(int i = 0; all_controllers[i] != nullptr; i++)
    {
        all_controllers_json.append(all_controllers[i]->to_json());
    }

    auto resp = HttpResponse::newHttpJsonResponse(all_controllers_json);

    callback(resp);

    controller_dbo::free_list(all_controllers);

}