#include "redisdatabase.h"
#include "utils/connectionstringhelper.h"

using namespace std::string_literals;

namespace myapp::persistence {

static std::string const s_Tag = "REDIS";

static void checked(redisContext* c)
{
    if (!c) 
        throw std::bad_alloc();
    if (c->err)
        throw std::runtime_error(c->errstr);
}

struct redis_reply final
{
    redis_reply() noexcept
      : reply_(nullptr, nullptr)
    {
    }

    redis_reply(void* p)
      : reply_(static_cast<redisReply*>(p), &::freeReplyObject)
    {
        throw_if_reply_error();
    }

    void throw_if_reply_error()
    {
        if (reply_->type == REDIS_REPLY_ERROR)
            throw std::runtime_error(reply_->str);
    }

    char* str()
    {
        if (reply_->type == REDIS_REPLY_STRING)
            return reply_->str;

        throw std::runtime_error("Not a string reply");
    }

private:
    std::unique_ptr<redisReply, decltype(&::freeReplyObject)> reply_;
};

redis_database::redis_database(std::string connection_string, std::shared_ptr<logging::logger> logger)
  : ctx_(nullptr)
  , logger_(std::move(logger))
  , ttl_(0)
{
    auto params = utils::split_parameters(connection_string);
    auto conn = utils::split_parameters(params[0], ':');

    if (conn.size() != 2)
        throw std::runtime_error("Invalid redis connection string, 'host:port' not present");

    auto host = conn[0];
    auto port = std::stoi(conn[1]);

    namespace_ = utils::extract_parameter(params, "namespace"s + utils::value_separator);
    if (!namespace_.empty() && !utils::ends_with(namespace_, ":"))
        namespace_.push_back(':');

    utils::extract_parameter_and_invoke_if_present(params, "ttl"s + utils::value_separator, [this] (std::string ttl)
    {
        ttl_ = std::stoi(ttl);
    });

    logger_->notice(s_Tag, "Connecting to '"s + host + ":"s + std::to_string(port) + "'"s);

    ctx_ = { redisConnect(host.c_str(), port), &::redisFree };
    checked(ctx_.get());
}

void redis_database::save_readout(std::string const& topic, std::string const& value)
{
    redis_reply r;

    if (ttl_ > 0)
    {
        r = redisCommand(ctx_.get(), "SETEX %s%s %d %s", namespace_.c_str(), topic.c_str(), ttl_, value.c_str());
    }
    else
    {
        r = redisCommand(ctx_.get(), "SET %s%s %s", namespace_.c_str(), topic.c_str(), value.c_str());
    }
}

std::string redis_database::last_readout(std::string const& topic)
{
    redis_reply r = redisCommand(ctx_.get(), "GET %s%s", namespace_.c_str(), topic.c_str());
    return r.str();
}

} /* namespace myapp::persistence */
