#include <netdb.h>
#include <models/schedule_dbo.h>
#include <models/tag_dbo.h>
#include <models/junction_tag_dbo.h>
#include <helpers.h>
#include "api_v1_schedules.h"
using namespace api::v1;

void
schedules::get_all(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
    schedule_dbo **all_schedules = schedule_dbo::get_all();
    Json::Value all_schedules_json(Json::arrayValue);

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

    Json::StreamWriterBuilder jw;

    auto resp = HttpResponse::newHttpJsonResponse(all_schedules_json);

    callback(resp);

    schedule_dbo::free_list(all_schedules);
}

void
schedules::get_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, const std::string& schedule_id_str)
{
    uuid_t schedule_id;
    if(schedule_dbo::parse_uid(schedule_id_str.c_str(), schedule_id))
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k400BadRequest);
        callback(resp);
        return;
    }

    schedule_dbo **schedules = schedule_dbo::get_by_simple("uid", schedule_id, (intptr_t) &sqlite3_bind_blob, sizeof(uuid_t));

    if(schedules[0])
    {
        auto resp = HttpResponse::newHttpJsonResponse(schedules[0]->to_json());

        callback(resp);
    }
    else
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k404NotFound);

        callback(resp);
    }
    schedule_dbo::free_list(schedules);
}

void
schedules::delete_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, const std::string& schedule_id_str)
{
    if(strcmp(schedule_id_str.c_str(), "off") == 0 || strcmp(schedule_id_str.c_str(), "on") == 0)
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k403Forbidden);
        callback(resp);

        return;
    }

    uuid_t schedule_id;
    if(schedule_dbo::parse_uid(schedule_id_str.c_str(), schedule_id))
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k400BadRequest);
        callback(resp);
        return;
    }
    schedule_dbo **schedules = schedule_dbo::get_by_simple("uid", schedule_id, (intptr_t) &sqlite3_bind_blob, sizeof(uuid_t));

    if(schedules[0])
    {
        auto resp = HttpResponse::newHttpResponse();
        if(!schedules[0]->remove())
        {
            resp->setStatusCode(k500InternalServerError);
        }

        callback(resp);
    }
    else
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k404NotFound);

        callback(resp);
    }
    schedule_dbo::free_list(schedules);
}

void
schedules::post_new(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
    Json::Value body = *req->jsonObject();

    bool set_name = body["name"].type() == Json::ValueType::stringValue;
    bool set_tags = body["tags"].type() == Json::ValueType::arrayValue;
    //bool set_periods = body["periods"].type() == Json::ValueType::arrayValue;

    if(!set_name || !set_name)
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k400BadRequest);
        callback(resp);
    }

    schedule_dbo new_schedule{};

    strncpy(new_schedule.name, body["name"].asCString(), 127);
    new_schedule.name[127] = '\0';
    uuid_generate(new_schedule.uid);
    new_schedule.periods = helpers::parse_periods(body["periods"]);

    if(!new_schedule.insert())
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k500InternalServerError);
        callback(resp);
    }
    else
    {
        auto resp = HttpResponse::newHttpJsonResponse(new_schedule.to_json());
        callback(resp);
    }
}

void
schedules::post_list(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback)
{
    Json::Value body = *req->jsonObject();

    Json::Value schedules_json(Json::arrayValue);

    for(int i = 0; i < body.size(); ++i)
    {
        bool set_name = body[i]["name"].type() == Json::ValueType::stringValue;
        bool set_tags = body[i]["tags"].type() == Json::ValueType::arrayValue;
        //bool set_periods = body[i]["periods"].type() == Json::ValueType::arrayValue;

        if(!set_name || !set_name)
        {
            auto resp = HttpResponse::newHttpResponse();
            resp->setStatusCode(k400BadRequest);
            callback(resp);
        }

        schedule_dbo new_schedule{};

        strncpy(new_schedule.name, body[i]["name"].asCString(), 127);
        new_schedule.name[127] = '\0';
        uuid_generate(new_schedule.uid);
        new_schedule.periods = helpers::parse_periods(body[i]["periods"]);

        if(new_schedule.insert())
        {
            schedules_json.append(new_schedule.to_json());
        }
    }

    auto resp = HttpResponse::newHttpJsonResponse(schedules_json);
    callback(resp);
}

void
schedules::put_one_by_id(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
                         const std::string &schedule_id_str)
{
    uuid_t schedule_id;
    if(schedule_dbo::parse_uid(schedule_id_str.c_str(), schedule_id))
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k400BadRequest);
        callback(resp);
        return;
    }
    schedule_dbo **schedules = schedule_dbo::get_by_simple("uid", schedule_id, (intptr_t) &sqlite3_bind_blob, sizeof(uuid_t));

    if(schedules[0] == nullptr)
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k404NotFound);

        callback(resp);
    }

    Json::Value body = *req->jsonObject();

    bool set_name = body["name"].type() == Json::ValueType::stringValue;
    bool set_tags = body["tags"].type() == Json::ValueType::arrayValue;
    bool set_periods = body["periods"].type() == Json::ValueType::arrayValue;

    if(set_name)
    {
        strncpy(schedules[0]->name, body["name"].asCString(), 127);
        schedules[0]->name[127] = '\0';
    }

    if(set_periods)
    {
        // if neither "off" nor "on" allow overwrite of periods
        if(strcmp(schedule_id_str.c_str(), "off") && strcmp(schedule_id_str.c_str(), "on"))
        {
            delete schedules[0]->periods;
            schedules[0]->periods = helpers::parse_periods(body["periods"]);
        }
    }

    if(set_tags)
    {
        junction_tag_dbo::remove_for_schedule(schedules[0]->id);

        for(int i = 0; i < body["tags"].size(); ++i)
        {
            const char *tag = body["tags"][i].asCString();
            int tag_id = tag_dbo::get_id(tag);
            if(tag_id == 0)
            {
                tag_dbo::save(tag_id, tag);
                tag_id = tag_dbo::get_id(tag);
            }
            junction_tag_dbo::insert(tag_id, 0, schedules[0]->id);
        }
    }

    if(!schedules[0]->update())
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k500InternalServerError);
        callback(resp);
    }
    else
    {
        auto resp = HttpResponse::newHttpJsonResponse(schedules[0]->to_json());
        callback(resp);
    }

    schedule_dbo::free_list(schedules);
}

void
schedules::get_by_tag(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback,
                       const std::string &tag)
{
    int tag_id = tag_dbo::get_id(tag.c_str());
    int *schedules_ids = junction_tag_dbo::get_schedules_for_tag_id(tag_id);
    if(schedules_ids == nullptr)
    {
        auto resp = HttpResponse::newHttpResponse();
        resp->setStatusCode(k500InternalServerError);
        callback(resp);
    }

    Json::Value schedules_json(Json::arrayValue);

    for(int i = 0; schedules_ids[i] != 0; ++i)
    {
        schedule_dbo *schedule = schedule_dbo::get_by_id(schedules_ids[i]);
        if(schedule)
        {
            schedules_json.append(schedule->to_json());
        }
    }

    auto resp = HttpResponse::newHttpJsonResponse(schedules_json);
    callback(resp);
    free(schedules_ids);
}