#include "ymapsdf_object_handler.h"

#include "magic_strings.h"
#include "masstransit.h"

#include <maps/libs/log8/include/log8.h>

#include <boost/format.hpp>
#include <fstream>
#include <iomanip>
#include <sstream>

namespace maps::wiki::masstransit {

namespace {

std::string
ftTypeId(bool allowFtTypeId)
{
    return allowFtTypeId ? ", ft.ft_type_id" : "";
}

std::string
ftSourceColumn()
{
    return ", (SELECT source_id FROM ft_source "
           "WHERE ft.ft_id=ft_source.ft_id "
           "AND source_type_id = " + value::SOURCE_TYPE_MTR + ") AS source_id";
}

std::string
parentId(bool allowParentId)
{
    return allowParentId ? ", ft.p_ft_id" : "";
}

std::string
nodeColumn(bool allowPointGeom)
{
    if (!allowPointGeom) {
        return "";
    }
    return ", ST_AsText(shape) AS shape ";
}

std::string
nodeJoins(bool allowPointGeom)
{
    if (!allowPointGeom) {
        return "";
    }
    return "LEFT JOIN ft_center ON (ft.ft_id = ft_center.ft_id) LEFT JOIN node USING (node_id) ";
}

std::string
nameColumn(bool allowNm)
{
    return allowNm
        ? ", (SELECT name FROM ft_nm WHERE ft_nm.ft_id=ft.ft_id AND name_type <= 1"
          " ORDER BY is_local DESC, name_type=1 DESC, lang, name LIMIT 1) AS name "
        : "";
}

std::string
attrColumns(const StringVector& attrs)
{
    std::ostringstream os;
    for (const auto& attr : attrs) {
        os << ", (SELECT value FROM ft_attr "
                "WHERE ft.ft_id = ft_attr.ft_id "
                "AND ft_attr.key = '" << attr << "') AS " << attr;
    }
    return os.str();
}

std::string
relColumns(
    const StringVector& rolesToMasters,
    const StringVector& rolesToSlaves)
{
    std::ostringstream os;
    for (const auto& role : rolesToMasters) {
        os << ", (SELECT master_ft_id FROM ft_ft "
            "WHERE role = '" << role << "' "
            "AND ft.ft_id = ft_ft.slave_ft_id) AS " << role;
    }
    for (const auto& role : rolesToSlaves) {
        os << ", (SELECT slave_ft_id FROM ft_ft "
            "WHERE role = '" << role << "' "
            "AND ft.ft_id = ft_ft.master_ft_id) AS " << role;
    }
    return os.str();
}

} // namespace

YmapsdfObjectHandler::YmapsdfObjectHandler(
        Masstransit& masstransit,
        ObjectMapBase& objectMap)
    : ObjectHandler(masstransit, objectMap)
{ }

void
YmapsdfObjectHandler::read(DBHelper& dBHelper)
{
    std::string idsQuery = loadIdsSql();
    std::vector<DBID> ids;
    ids.reserve(masstransit_.params.batchSize);
    const auto rows = dBHelper.execRead(idsQuery);

    auto readBatch = [this](DBHelper& dBHelper, std::vector<DBID> ids) {
        auto query = str(boost::format(loadRowsSqlTemplate()) % toString(ids));
        auto rows = dBHelper.execRead(query);
        for (const auto& tuple : rows) {
            try {
                addObject(tuple);
            } catch (const maps::Exception& e) {
                DATA_ERROR() << "Error in " << name() << " object "
                    << getAttr<DBID>(tuple, ymapsdf::ID) << ": " << e.what();
                ERROR() << e;
                masstransit_.fail("read");
            } catch (std::exception& e) {
                DATA_ERROR() << "Error in " << name() << " object "
                    << getAttr<DBID>(tuple, ymapsdf::ID) << ": " << e.what();
                masstransit_.fail("read");
            }
        }

    };

    for (const pqxx::row& row : rows) {
        ids.push_back(getAttr<DBID>(row, ymapsdf::ID));
        if (ids.size() >= masstransit_.params.batchSize) {
            readBatch(dBHelper, ids);
            ids.clear();
        }
    }
    if (!ids.empty()) {
        readBatch(dBHelper, ids);
    }
}

void
YmapsdfObjectHandler::update()
{
    for (const auto& object : objects()) {
        try {
            updateObject(*object);
        } catch (const maps::Exception& e) {
            ERROR() << e;
            masstransit_.fail("update");
        } catch (const std::exception& e) {
            ERROR() << "Error: " << e.what();
            masstransit_.fail("update");
        }
    }
}

std::string
YmapsdfObjectHandler::loadIdsSql() const
{
    const auto ftTypeIds = ftTypes();
    ASSERT(!ftTypeIds.empty());
    return "SELECT ft_id AS id FROM ft WHERE ft_type_id IN " + toString(ftTypeIds) + ";";
}

std::string
YmapsdfObjectHandler::loadRowsSqlTemplate() const
{
    std::ostringstream query;
    query << "SELECT ft.ft_id AS id"
        << ftTypeId(allowFtTypeId())
        << ftSourceColumn()
        << parentId(allowParentId())
        << nodeColumn(allowPointGeom())
        << nameColumn(allowNm())
        << attrColumns(attrs())
        << relColumns(rolesToMasters(), rolesToSlaves())
        << extraSelectColumns()
        << " FROM ft "
        << nodeJoins(allowPointGeom())
        << "WHERE ft.ft_id IN %1%;";
    return query.str();
}

} // namespace maps::wiki::masstransit
