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

namespace maps {
namespace wiki {
namespace tasks {

namespace sql {
namespace col {
const std::string TASK_ID = "task_id";
const std::string COMMIT_ID = "commit_id";
const std::string SYNCHRONIZED = "synchronized";
} // namespace col
} // namespace sql

template<typename CommitIds>
CommitRecords
CommitRecordGateway::getCommits(TaskId taskId, CommitIds&& commitIds) const
{
    if (commitIds.empty()) {
        return {};
    }

    return getCommitsInternal<CommitIds>(taskId, commitIds);
}

template<typename CommitIds>
CommitIds
CommitRecordGateway::getCommitIds(TaskId taskId) const
{
    CommitIds result;
    for (auto& commit : getCommits(taskId)) {
        result.push_back(commit.commitId);
    }
    return result;
}

template<typename CommitIds>
CommitRecords
CommitRecordGateway::getCommitsInternal(
    TaskId taskId,
    const boost::optional<CommitIds>& commitIds) const
{
    if (commitIds && commitIds->empty()) {
        return {};
    }

    std::ostringstream query;
    query << "SELECT " << sql::col::TASK_ID << ", "
                       << sql::col::COMMIT_ID << ", "
                       << sql::col::SYNCHRONIZED << "\n"
          << "FROM " << tableName_ << "\n"
          << "WHERE " << sql::col::TASK_ID << " = " << taskId;
    if (commitIds) {
        query << "AND " << sql::col::COMMIT_ID << "\n"
              << "IN (" << common::join(commitIds.get(), ", ") <<  ")";
    }
    query << ";";

    auto result = txn_->exec(query.str());
    CommitRecords commits;

    for (const auto& row: result) {
        auto taskId = row[sql::col::TASK_ID].as<TaskId>();
        auto commitId = row[sql::col::COMMIT_ID].as<CommitId>();
        auto status = static_cast<CommitStatus>(row[sql::col::SYNCHRONIZED].as<bool>());
        commits.emplace_back(taskId, commitId, status);
    }
    return commits;
}

template<typename CommitIds>
void
CommitRecordGateway::addCommits(
    TaskId taskId,
    CommitIds&& commitIds,
    CommitStatus status)
{
    if (commitIds.empty()) {
        return;
    }

    auto existingCommits = getCommits(taskId, commitIds);
    if (!existingCommits.empty()) {
        throw CommitRecordInvalid()
            << "Can't insert duplicate commit ids for task id " << taskId
            << ": [" << common::join(existingCommits,
                    [](const CommitRecord& cr){ return cr.commitId; }, ", ")
            << "]";
    }

    std::ostringstream query;
    query << "INSERT INTO " << tableName_ << " ("
            << sql::col::TASK_ID << ", "
            << sql::col::COMMIT_ID << ", "
            << sql::col::SYNCHRONIZED << ")\n"
          << " VALUES "
          << common::join(commitIds, [taskId, status](CommitId commitId){
                 std::ostringstream os;
                 os << "(" << taskId << ", " << commitId << ", " << status << ")";
                 return os.str();
             }, ",")
          << ";";
    txn_->exec(query.str());
}

template<typename CommitIds>
std::size_t
CommitRecordGateway::removeCommits(TaskId taskId, CommitIds&& commitIds)
{
    if (commitIds.empty()) {
        return 0;
    }

    std::ostringstream query;
    query << "DELETE FROM " << tableName_ << "\n"
          << "WHERE " << sql::col::TASK_ID << " = " << taskId << "\n"
          << "AND " << sql::col::COMMIT_ID << "\n"
          << "IN (" << common::join(commitIds, ", ") <<  ");";
    auto result = txn_->exec(query.str());
    return result.affected_rows();
}

template<typename CommitIds>
std::size_t
CommitRecordGateway::updateStatus(
    TaskId taskId,
    CommitIds&& commitIds,
    CommitStatus status)
{
    if (commitIds.empty()) {
        return {};
    }

    std::ostringstream query;
    query << "UPDATE " << tableName_ << "\n"
          << "SET " << sql::col::SYNCHRONIZED << " = " << status << "\n"
          << "WHERE " << sql::col::TASK_ID << " = " << taskId << "\n"
          << "AND " << sql::col::COMMIT_ID << "\n"
          << "IN (" << common::join(commitIds, ", ") <<  ");";

    auto result = txn_->exec(query.str());
    return result.affected_rows();
}

} // namespace tasks
} // namespace wiki
} // namespace maps

