#include "sync_table.h"

#include <maps/wikimap/mapspro/services/editor/src/sync/db_helpers.h>
#include <maps/wikimap/mapspro/services/editor/src/revision_meta/bbox_helpers.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/common/include/profiletimer.h>
#include <yandex/maps/wiki/common/pg_retry_helpers.h>
#include <yandex/maps/wiki/common/retry_duration.h>
#include <sstream>

namespace maps::wiki::tool {

namespace {

size_t
run(pgpool3::Pool& pgPool,
    const std::string& tableContext,
    const std::string& searchPath,
    const std::string& query)
{
    size_t rowsNumber = 0;
    common::execCommitWithRetries(
        pgPool,
        tableContext,
        searchPath,
        {},
        [&] (pqxx::transaction_base& txn) {
            rowsNumber = txn.exec(query).affected_rows();
        }
    );
    return rowsNumber;
}

size_t
copyBBoxes(
    pgpool3::Pool& pgPool,
    TBranchId sourceBranchId,
    TBranchId targetBranchId)
{
    auto name =
        "copyBBoxes " + std::to_string(sourceBranchId) +
               " -> " + std::to_string(targetBranchId);

    size_t count = 0;
    common::execCommitWithRetries(
        pgPool,
        name,
        {},
        {},
        [&] (pqxx::transaction_base& txn) {
            count = DatabaseBBoxProvider::copy(txn, sourceBranchId, targetBranchId);
        }
    );
    return count;
}

} // namespace

SyncTableQueries
buildSyncTableQueries(
    pgpool3::Pool& pgPool,
    const std::string& sourceSchemaName,
    const std::string& targetSchemaName,
    const std::string& tableName)
{
    auto rows = common::retryDuration([&] {
        auto work = pgPool.masterReadOnlyTransaction();

        auto query =
            "SELECT indexname, indexdef, indisprimary"
            " FROM pg_indexes, pg_index"
            " WHERE schemaname=" + work->quote(targetSchemaName) +
              " AND tablename=" + work->quote(tableName) +
              " AND indexrelid = (schemaname||'.'||indexname)::regclass";
        return work->exec(query);
    });

    SyncTableQueries queries;
    queries.searchPath = "SET search_path=" + targetSchemaName + ", public;";

    queries.createData =
        "INSERT INTO " + tableName + " SELECT * FROM " +
        sourceSchemaName + "." + tableName;

    std::ostringstream dropIndexesQuery;
    for (const auto& row : rows) {
        auto indexName = row[0].as<std::string>();
        queries.indexName2definition[indexName] = row[1].as<std::string>();
        if (row[2].as<bool>()) { // is primary
            dropIndexesQuery << "ALTER TABLE " << tableName +
                     " DROP CONSTRAINT " + indexName << ';';
            queries.addPrimaryKey =
                "ALTER TABLE " + tableName +
                " ADD CONSTRAINT " + indexName +
                " PRIMARY KEY USING INDEX " + indexName;
        } else {
            dropIndexesQuery << "DROP INDEX " << indexName << ';';
        }
    }
    queries.dropIndexes = dropIndexesQuery.str();
    return queries;
}

SyncTable::SyncTable(
        ExecutionStatePtr executionState,
        const std::string& tableContext,
        DisplayRows displayRows,
        Runner runner)
    : executionState_(executionState)
    , tableContext_(tableContext)
    , displayRows_(displayRows)
    , runner_(std::move(runner))
{}

void
SyncTable::operator() () const
{
    try {
        ProfileTimer pt;
        if (executionState_->isOk()) {
            INFO() << "SYNC TABLE " << tableContext_ << " start";
            auto rows = runner_();
            if (executionState_->isOk()) {
                INFO() << "SYNC TABLE " << tableContext_
                       << (rows && displayRows_ == DisplayRows::Yes
                            ? (" " + std::to_string(*rows) + " rows")
                            : std::string())
                       << " : " << pt.getElapsedTime();
                return;
            }
        }
        INFO() << "SYNC TABLE " << tableContext_ << " canceled";
    }
    catch (const maps::Exception& e) {
        std::ostringstream ss;
        ss << e;
        handleError(ss.str().c_str());
    }
    catch (const std::exception& e) {
        handleError(e.what());
    }
    catch (const pqxx::failure& e) {
        handleError(e.what());
    }
    catch (...) {
        handleError("unknown error");
    }
}

void
SyncTable::handleError(const char* errorMessage) const
{
    executionState_->fail = true;
    ERROR() << "SYNC TABLE FAILURE " << tableContext_
            << " Error: " << errorMessage;
}


SyncTableQuery::SyncTableQuery(
        pgpool3::Pool& pgPool,
        ExecutionStatePtr executionState,
        const std::string& tableContext,
        const std::string& searchPath,
        const std::string& query,
        DisplayRows displayRows)
    : SyncTable(
        executionState,
        tableContext,
        displayRows,
        [=, &pgPool] {
            return run(pgPool, tableContext, searchPath, query);
        })
{}


SyncTableRevisionMeta::SyncTableRevisionMeta(
        pgpool3::Pool& pgPool,
        ExecutionStatePtr executionState,
        TBranchId sourceBranchId,
        TBranchId targetBranchId)
    : SyncTable(
        executionState,
        "revision_meta.*",
        SyncTable::DisplayRows::Yes,
        [=, &pgPool] {
            return copyBBoxes(pgPool, sourceBranchId, targetBranchId);
        })
{}

} // namespace maps::wiki::tool
