#include <yandex/maps/wiki/social/fbapi_issue.h>

#include "factory.h"
#include "magic_strings.h"

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

#include <cstddef>
#include <string>
#include <vector>

namespace maps::wiki::social {

namespace {

const std::string FIELDS_TO_SELECT = common::join(
    std::vector<std::string>{
        sql::col::ID,
        sql::col::CREATED_AT,
        sql::col::ISSUE_ID,
        sql::col::FEEDBACK_TASK_ID,
        sql::col::RAW_DATA
    },
    [](const std::string& field) {
        return sql::table::FBAPI_ISSUE + "." + field;
    },
    ","
);

FbapiIssues fbapiIssuesFromRows(const pqxx::result& rows)
{
    FbapiIssues result;
    for (const auto& row: rows) {
        result.push_back(Factory::component<FbapiIssue>(row));
    }
    return result;
}

} // namespace

std::string FbapiIssueFilter::whereClause() const
{
    std::stringstream query;

    query << "TRUE";

    if (feedbackTaskId_) {
        query << " AND " << sql::col::FEEDBACK_TASK_ID << " = " << *feedbackTaskId_;
    }
    if (issueIds_) {
        query << " AND " << sql::col::ISSUE_ID << " " << common::sqlInCondition(*issueIds_);
    }
    return query.str();
}

FbapiIssue::FbapiIssue(const pqxx::row& row)
    : id_(row[sql::col::ID].as<TId>())
    , createdAt_(chrono::parseSqlDateTime(row[sql::col::CREATED_AT].as<std::string>()))
    , issueId_(row[sql::col::ISSUE_ID].as<std::string>())
    , feedbackTaskId_(row[sql::col::FEEDBACK_TASK_ID].as<TId>())
    , rawData_(json::Value::fromString(row[sql::col::RAW_DATA].as<std::string>()))
{ }

FbapiIssues fbapiIssuesUnmatchedHash(
    pqxx::transaction_base& socialTxn)
{
    std::stringstream query;
    query << "SELECT " << FIELDS_TO_SELECT
          << " FROM " << sql::table::FBAPI_ISSUE
          << " JOIN " << sql::table::FEEDBACK_TASK
          << " ON(" << sql::table::FBAPI_ISSUE << "." << sql::col::FEEDBACK_TASK_ID
          << " = " << sql::table::FEEDBACK_TASK << "." << sql::col::ID << ")"
          << " WHERE " << sql::table::FEEDBACK_TASK << "." << sql::col::HASH_VALUE
          << " != " << sql::table::FBAPI_ISSUE << "." << sql::col::SYNC_HASH_VALUE;

    return fbapiIssuesFromRows(socialTxn.exec(query.str()));
}

FbapiIssues fbapiIssuesByFilter(
    pqxx::transaction_base& socialTxn,
    const FbapiIssueFilter& filter)
{
    std::stringstream query;
    query << "SELECT " << FIELDS_TO_SELECT << " FROM " << sql::table::FBAPI_ISSUE
          << " WHERE " << filter.whereClause();

    return fbapiIssuesFromRows(socialTxn.exec(query.str()));
}

std::optional<FbapiIssue> fbapiIssueById(
    pqxx::transaction_base& socialTxn,
    TId issueId)
{
    std::stringstream query;
    query << "SELECT " << FIELDS_TO_SELECT << " FROM " << sql::table::FBAPI_ISSUE << " ";
    query << "WHERE " << sql::col::ID << " = " << issueId;

    auto rows = socialTxn.exec(query);
    if (rows.empty()) {
        return std::nullopt;
    }
    return Factory::component<FbapiIssue>(rows[0]);
}

FbapiIssue addFbapiIssue(
    pqxx::transaction_base& socialTxn,
    const std::string& issueId,
    TId feedbackTaskId,
    const json::Value& rawData)
{
    std::stringstream query;

    query << "INSERT INTO " << sql::table::FBAPI_ISSUE << " "
        << "("
            << sql::col::ISSUE_ID
            << ", " << sql::col::FEEDBACK_TASK_ID
            << ", " << sql::col::RAW_DATA
        << ") VALUES ("
            << socialTxn.quote(issueId)
            << ", " << feedbackTaskId
            << ", " << socialTxn.quote((json::Builder() << rawData).str())
        << ") RETURNING " << FIELDS_TO_SELECT;

    auto rows = socialTxn.exec(query.str());
    ASSERT(rows.size() == 1);
    return Factory::component<FbapiIssue>(rows[0]);
}

FbapiIssue updateFbapiIssueHashValue(
    pqxx::transaction_base& socialTxn,
    TId issueId,
    const std::string& newHashValue)
{
    std::stringstream query;
    query << "UPDATE " << sql::table::FBAPI_ISSUE << " "
        << " SET " << sql::col::SYNC_HASH_VALUE << " = "
        << socialTxn.quote(newHashValue)
        << " WHERE " << sql::col::ID << " = " << issueId
        << " RETURNING " << FIELDS_TO_SELECT;

    auto rows = socialTxn.exec(query);
    REQUIRE(rows.size() == 1, "not unique fbapiIssue by id = " << issueId);
    return Factory::component<FbapiIssue>(rows[0]);
}

} // namespace maps::wiki::social
