#include <maps/wikimap/mapspro/libs/query_builder/include/select_query.h>

#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/string_utils.h>
#include <maps/libs/common/include/environment.h>
#include <maps/libs/log8/include/log8.h>

namespace maps::wiki::query_builder {

namespace {

bool inUnittest()
{
    static const bool inUnittest =
        maps::common::getYandexEnvironment() == maps::common::Environment::Unittest;
    return inUnittest;
}

double makeSeed() {
    if (inUnittest()) {
        return -1.;
    }
    std::srand(std::time(0));
    // postgres need rand seed in [-1, 1]
    return static_cast<double>(std::rand()) / RAND_MAX * 2 - 1;
}

} // namespace

SelectQuery::SelectQuery(
    std::string table,
    std::vector<std::string> columns,
    WhereConditions whereConditions)
    : table_(std::move(table))
    , columns_(std::move(columns))
    , whereConditions_(std::move(whereConditions))
    , forUpdate_(false)
    , updateSeed_(false)
    , nowait_(false)
{}

SelectQuery::SelectQuery(
    const JoinSequence& join,
    std::vector<std::string> columns,
    WhereConditions whereConditions)
    : SelectQuery(join.asString(), columns, whereConditions)
{}

SelectQuery::SelectQuery(
    std::string table, WhereConditions whereConditions)
    : table_(std::move(table))
    , columns_({"*"})
    , whereConditions_(std::move(whereConditions))
    , forUpdate_(false)
    , updateSeed_(false)
    , nowait_(false)
{}

SelectQuery::SelectQuery(
    const JoinSequence& join, WhereConditions whereConditions)
    : SelectQuery(join.asString(), whereConditions)
{}

SelectQuery& SelectQuery::distinctOn(std::vector<std::string> columns)
{
    distinctOnColumns_ = std::move(columns);
    return *this;
}

SelectQuery& SelectQuery::groupBy(std::vector<std::string> groupByColumns)
{
    groupByColumns_ = std::move(groupByColumns);
    return *this;
}

SelectQuery& SelectQuery::orderBy(std::string order)
{
    order_ = std::move(order);
    return *this;
}

SelectQuery& SelectQuery::offset(size_t offset)
{
    offset_ = offset;
    return *this;
}

SelectQuery& SelectQuery::limit(size_t limit)
{
    limit_ = limit;
    return *this;
}

SelectQuery& SelectQuery::forUpdate()
{
    forUpdate_ = true;
    return *this;
}

SelectQuery& SelectQuery::updateSeed()
{
    updateSeed_ = true;
    return *this;
}

SelectQuery& SelectQuery::nowait()
{
    nowait_ = true;
    return *this;
}

std::string SelectQuery::asString(const pqxx::transaction_base& txn) const
{
    REQUIRE(
        !columns_.empty(),
        "Cannot build empty select query for table " << table_);
    std::ostringstream query;

    if (updateSeed_) {
        query << "SELECT SETSEED(" + std::to_string(makeSeed()) + ");\n";
    }

    query << "SELECT ";
    if (!distinctOnColumns_.empty()) {
        query << "DISTINCT ON (" << maps::wiki::common::join(distinctOnColumns_, ", ") << ") ";
    }

    query << maps::wiki::common::join(columns_, ", ")
        << " FROM " << table_;
    if (!whereConditions_.empty()) {
        query << ' ' << whereConditions_.asString(txn);
    }

    if (!groupByColumns_.empty()) {
        query << " GROUP BY " << maps::wiki::common::join(groupByColumns_, ", ");
    }

    if (order_) {
        query << " ORDER BY " << *order_;
    }
    if (offset_) {
        query << " OFFSET " << *offset_;
    }
    if (limit_) {
        query << " LIMIT " << *limit_;
    }
    if (forUpdate_) {
        query << " FOR UPDATE";
        if (nowait_) {
            query << " NOWAIT";
        }
    }

    return query.str();
}

auto SelectQuery::introspect() const
{
    return std::tie(table_, columns_, whereConditions_, order_, offset_, limit_);
}

pqxx::result SelectQuery::exec(pqxx::transaction_base& txn) const
{
    const auto query = asString(txn);
    DEBUG() << "Perform query: " << query;
    return txn.exec(query);
}

} // namespace maps::wiki::query_builder
