#include <yandex/maps/wiki/revision/commit.h>

#include "commit_data.h"
#include "context.h"
#include "helpers.h"
#include "sql_strings.h"
#include "query_generator.h"

#include <maps/libs/common/include/exception.h>

namespace maps::wiki::revision {

using namespace helpers;

namespace {

const std::string LOAD_COMMIT_PREFIX =
    "SELECT " +
        sql::col::ID + ", " +
        sql::col::APPROVE_ORDER + ", " +
        sql::col::STATE + ", " +
        sql::col::IS_TRUNK + ", " +
        sql::col::STABLE_BRANCH_ID + ", " +
        sql::col::CREATED_AT + ", " +
        sql::col::CREATED_BY + ", "
        " hstore_to_array(" + sql::col::ATTRIBUTES + ") AS " + sql::col::ATTRIBUTES +
    " FROM " + sql::table::COMMIT + " c"
    " WHERE ";

const std::string LOAD_COMMIT_ID_PREFIX =
    "SELECT " + sql::col::ID +
    " FROM " + sql::table::COMMIT + " c"
    " WHERE ";

const std::string COUNT_ALIAS = "cnt";

const std::string LOAD_USER_COUNT_SELECT =
    "SELECT " +
        sql::col::CREATED_BY + ", "
        "count(1) as " + COUNT_ALIAS +
    " FROM " + sql::table::COMMIT + " c"
    " WHERE ";

const std::string LOAD_USER_COUNT_GROUP_BY = " GROUP BY " + sql::col::CREATED_BY;

namespace attr {
const std::string ACTION = "action";
const std::string SOURCE = "source";
} // namespace attr

namespace value {
const std::string IMPORT = "import";
const std::string EMPTY;
} // namespace value

std::string makeQuery(
    pqxx::transaction_base& work, const filters::FilterExpr& expr)
{
    filters::Context ctx(work.conn());
    return expr.evalQuery(ctx);
}

} // namespace

Commit::Commit(CommitData&& data)
    : data_(std::make_shared<CommitData>(std::move(data)))
{}

Commit
Commit::load(pqxx::transaction_base& work, DBID id)
{
    checkCommitId(id);

    auto r = work.exec(
        LOAD_COMMIT_PREFIX + sql::col::ID + " = " + std::to_string(id));
    REQUIRE(r.size() == 1, "can not load commit data, commit id: " << id);
    return Commit(CommitData::load(r[0]));
}

std::list<Commit>
Commit::load(pqxx::transaction_base& work, const filters::FilterExpr& expr)
{
    auto r = work.exec(LOAD_COMMIT_PREFIX + makeQuery(work, expr));

    std::list<Commit> result;
    for (const auto& row : r) {
        result.emplace_back(CommitData::load(row));
    }
    return result;
}

std::vector<DBID>
Commit::loadIds(pqxx::transaction_base& work, const filters::FilterExpr& expr)
{
    auto r = work.exec(LOAD_COMMIT_ID_PREFIX + makeQuery(work, expr));

    std::vector<DBID> result;
    result.reserve(r.size());
    for (const auto& row : r) {
        result.emplace_back(row[0].as<DBID>());
    }
    return result;
}

std::map<UserID, size_t>
Commit::loadUserCommitsCount(pqxx::transaction_base& work, const filters::FilterExpr& expr)
{
    auto r = work.exec(
        LOAD_USER_COUNT_SELECT + makeQuery(work, expr) + LOAD_USER_COUNT_GROUP_BY);

    std::map<UserID, size_t> result;
    for (const auto& row : r) {
        result[row[sql::col::CREATED_BY].as<UserID>()] = row[COUNT_ALIAS].as<size_t>();
    }
    return result;
}

DBID
Commit::id() const
{
    return data_->id;
}

DBID
Commit::approveOrder() const
{
    return data_->approveOrder;
}

CommitState
Commit::state() const
{
    return data_->state;
}

UserID
Commit::createdBy() const
{
    return data_->createdBy;
}

const std::string&
Commit::createdAt() const
{
    return data_->createdAt;
}

chrono::TimePoint
Commit::createdAtTimePoint() const
{
    return chrono::parseSqlDateTime(createdAt());
}


const Attributes&
Commit::attributes() const
{
    return data_->attributes();
}

const std::string&
Commit::action() const
{
    const auto& attributes = data_->attributes();
    auto it = attributes.find(attr::ACTION);
    return it != attributes.end()
        ? it->second
        : value::IMPORT;
}

const std::string&
Commit::source() const
{
    const auto& attributes = data_->attributes();
    auto it = attributes.find(attr::SOURCE);
    return it != attributes.end()
        ? it->second
        : value::EMPTY;
}

void
Commit::setAttributes(pqxx::transaction_base& work, const Attributes& attrs)
{
    return data_->setAttributes(work, {work, attrs});
}

void
Commit::addAttribute(pqxx::transaction_base& work, const std::string& key, const std::string& value)
{
    return data_->addAttribute(work, key, value);
}

bool
Commit::inTrunk() const
{
    return data_->inTrunk();
}

bool
Commit::inStable() const
{
    return data_->inStable();
}

bool
Commit::isMerged() const
{
    return data_->merged();
}

DBID
Commit::stableBranchId() const
{
    return data_->stableBranchId;
}

DBIDSet
Commit::revertedCommitIds() const
{
    return data_->revertedCommitIds();
}

DBIDSet
Commit::revertedDirectlyCommitIds() const
{
    return data_->revertedDirectlyCommitIds();
}

DBIDSet
Commit::revertingCommitIds() const
{
    return data_->revertingCommitIds();
}

OptionalDBID
Commit::revertingDirectlyCommitId() const
{
    return data_->revertingDirectlyCommitId();
}

} // namespace maps::wiki::revision
