#include "db_helper.h"

#include "magic_strings.h"

#include <maps/libs/common/include/file_utils.h>
#include <yandex/maps/wiki/common/retry_duration.h>
#include <yandex/maps/wiki/tasks/ymapsdf.h>

#include <sstream>

namespace maps::wiki::masstransit {

namespace {

void
setSearchPath(pqxx::transaction_base& txn, const std::string& schemaName)
{
    txn.set_variable("search_path", schemaName + ",public");
}

} // namespace

DBHelper::DBHelper(pgpool3::Pool& ymapsdfPool, const std::string& schemaName)
    : ymapsdfPool_(ymapsdfPool)
    , schemaName_(schemaName)
{ }

pqxx::result
DBHelper::execRead(const std::string& query)
{
    return common::retryDuration([&] {
        auto txn = slaveTransaction();
        return exec(*txn, query);
    });
}

pqxx::result
DBHelper::execWrite(const std::string& query)
{
    return common::retryDuration([&] {
        auto txn = writeableTransaction();
        auto res = exec(*txn, query);
        commit(*txn);
        return res;
    });
}

void
DBHelper::dropSchema()
{
    std::ostringstream dropQuery;
    dropQuery << "DROP SCHEMA IF EXISTS " << schemaName_ << " CASCADE;";
    execWrite(dropQuery.str());
}

void
DBHelper::createSchema()
{
    std::ostringstream createQuery;
    createQuery << "CREATE SCHEMA " << schemaName_;
    execWrite(createQuery.str());

    auto scriptPath = tasks::ymapsdf::getScriptPath("ymapsdf_import_create.sql");
    execWrite(maps::common::readFileToString(scriptPath));
}

void
DBHelper::finalizeSchema()
{
    auto scriptPath = tasks::ymapsdf::getScriptPath("ymapsdf_import_finalize.sql");
    execWrite(maps::common::readFileToString(scriptPath));
}

DBID
DBHelper::maxId()
{
    auto scriptPath = tasks::ymapsdf::getScriptPath("max_id.sql");
    const auto rows = execRead(maps::common::readFileToString(scriptPath));
    ASSERT(rows.size() == 1);
    ASSERT(rows[0].size() == 1);
    return rows[0][0].as<DBID>();
}

pgpool3::TransactionHandle
DBHelper::writeableTransaction()
{
    auto txn = ymapsdfPool_.masterWriteableTransaction();
    setSearchPath(*txn, schemaName_);
    return txn;
}

pgpool3::TransactionHandle
DBHelper::slaveTransaction()
{
    auto txn = ymapsdfPool_.slaveTransaction();
    setSearchPath(*txn, schemaName_);
    return txn;
}

pqxx::result
DBHelper::exec(pqxx::transaction_base& txn, const std::string& query)
{
    try {
        return txn.exec(query);
    } catch (const pqxx::failure& ex) {
        throw RuntimeError() << "pqxx exec error: " << ex.what() << "\n query:\"" << query << "\"";
    } catch (const std::exception& ex) {
        throw RuntimeError() << "std exec error: " << ex.what() << "\n query:\"" << query << "\"";
    }
}

void
DBHelper::commit(pqxx::transaction_base& txn)
{
    try {
        txn.commit();
    } catch (const pqxx::failure& ex) {
        throw RuntimeError() << "pqxx commit error: " << ex.what();
    } catch (const std::exception& ex) {
        throw RuntimeError() << "std commit error: " << ex.what();
    }
}

} // namespace maps::wiki::masstransit
