#include <yandex/maps/wiki/social/involvement_stat.h>
#include <yandex/maps/wiki/social/common.h>
#include "magic_strings.h"

#include <yandex/maps/wiki/common/string_utils.h>

#include <cstddef>
#include <cstdint>
#include <functional>
#include <string>
#include <vector>

namespace maps::wiki::social {

namespace {

const std::string INVOLVEMENT_STATS_COLUMNS = common::join(
    std::vector<std::string>{
        sql::col::INVOLVEMENT_ID,
        sql::col::TYPE,
        sql::col::VALUE,
    },
    ","
);

const std::string INVOLVEMENT_STATS_PKEY = common::join(
    std::vector<std::string>{
        sql::col::INVOLVEMENT_ID,
        sql::col::TYPE
    },
    ","
);

} //anonymous namespace

InvolvementStat::InvolvementStat(const pqxx::row& row)
    : involvementId_(row[sql::col::INVOLVEMENT_ID].as<TId>())
    , type_(row[sql::col::TYPE].as<std::string>())
    , value_(row[sql::col::VALUE].as<int64_t>())
    , valueAddition_(0)
    , state_(State::Existent)
{
}

InvolvementStat::InvolvementStat(
    TId involvementId,
    std::string type
)
    : involvementId_(involvementId)
    , type_(std::move(type))
    , value_(0)
    , valueAddition_(0)
    , state_(State::New)
{
}

TId
InvolvementStat::involvementId() const
{
    return involvementId_;
}

const std::string&
InvolvementStat::type() const
{
    return type_;
}

int64_t
InvolvementStat::value() const
{
    return (value_ + valueAddition_);
}

InvolvementStat&
InvolvementStat::operator+= (int64_t addition)
{
    valueAddition_ += addition;
    return *this;
}

InvolvementStats
InvolvementStat::byInvolvementId(
    pqxx::transaction_base& socialTxn,
    TId involvementId
)
{
    std::ostringstream query;
    query <<
        "SELECT " << INVOLVEMENT_STATS_COLUMNS << " " <<
        "FROM " << sql::table::INVOLVEMENT_STATS << " " <<
        "WHERE " << sql::col::INVOLVEMENT_ID << "=" << involvementId
    ;
    auto pqxxResult = socialTxn.exec(query.str());
    InvolvementStats result;
    for (const auto& row: pqxxResult) {
        result.emplace_back(InvolvementStat(row));
    }
    return result;
}

InvolvementStats
InvolvementStat::byInvolvements(
    pqxx::transaction_base& socialTxn,
    const Involvements& involvements
)
{
    if (involvements.empty()) {
        return {};
    }
    std::ostringstream query;
    query <<
        "SELECT " << INVOLVEMENT_STATS_COLUMNS << " " <<
        "FROM " << sql::table::INVOLVEMENT_STATS << " " <<
        "WHERE " << sql::col::INVOLVEMENT_ID << " IN " << "(" <<
            common::join(
                involvements,
                std::mem_fn(&Involvement::id),
                ","
            ) <<
        ")"
    ;
    auto pqxxResult = socialTxn.exec(query.str());
    InvolvementStats result;
    for (const auto& row: pqxxResult) {
        result.emplace_back(InvolvementStat(row));
    }
    return result;
}

void
InvolvementStat::writeToDatabase(pqxx::transaction_base& socialTxn)
{
    if (valueAddition_ == 0) {
        //nothing to update
        return;
    }
    std::ostringstream query;
    if (state_ == State::New) {
        query <<
            "INSERT INTO " << sql::table::INVOLVEMENT_STATS << " " <<
            "(" << INVOLVEMENT_STATS_COLUMNS << ")" << " " <<
            "VALUES (" <<
                involvementId_ << ", " <<
                socialTxn.quote(type_) << ", " <<
                valueAddition_ <<
            ") "
        ;
    } else {
        query <<
            "UPDATE " << sql::table::INVOLVEMENT_STATS << " " <<
            "SET " <<
                sql::col::VALUE << " = " <<
                sql::col::VALUE << " + " << valueAddition_ << " " <<
            "WHERE "
            "(" << INVOLVEMENT_STATS_PKEY << ")" <<
            " = " <<
            "(" << involvementId_ << ", " << socialTxn.quote(type_) << ") "
        ;
    }
    socialTxn.exec(query.str());
    value_ += valueAddition_;
    valueAddition_ = 0;
    state_ = State::Existent;
}

} //namespace maps::wiki::social
