#include <maps/wikimap/mapspro/services/tasks_feedback/src/import_indoor_feedback_worker/lib/load_from_yt.h>
#include <maps/wikimap/mapspro/services/tasks_feedback/src/import_indoor_feedback_worker/lib/to_tasks.h>
#include <maps/wikimap/mapspro/services/tasks_feedback/src/import_indoor_feedback_worker/lib/upload_tasks.h>

#include <maps/wikimap/mapspro/services/tasks_feedback/src/common/last_processed_time.h>
#include <maps/wikimap/mapspro/services/tasks_feedback/src/common/time.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/common/include/exception.h>
#include <yandex/maps/pgpool3utils/pg_advisory_mutex.h>
#include <yandex/maps/wiki/common/default_config.h>
#include <yandex/maps/wiki/common/extended_xml_doc.h>
#include <yandex/maps/wiki/common/pg_advisory_lock_ids.h>
#include <yandex/maps/wiki/common/pgpool3_helpers.h>
#include <yandex/maps/wiki/tasks/status_writer.h>

#include <mapreduce/yt/util/wait_for_tablets_state.h>
#include <yandex/maps/wiki/common/yt.h>

#include <optional>
#include <pqxx/pqxx>
#include <sstream>
#include <string>

namespace mwc = maps::wiki::common;
namespace mwt = maps::wiki::tasks_feedback;

namespace {

const std::string MODULE_NAME = "wiki-import-indoor-feedback-worker";
const std::string WATERMARK_TABLE = "service.last_processed_indoor_feedback";

const TString YT_PROXY_NAME = "hahn";
const TString YT_DYNAMIC_TABLE_PATH = "//home/offline_data/walkers/maps/indoor/assignments_export";

std::optional<mwt::indoor_feedback::YTData>
fetchDataFromYT(const mwt::TimeIntervalMs& timeInterval)
{
    // Load YT data.
    auto ytClientPtr = mwc::yt::createYtClient(YT_PROXY_NAME);
    auto ytData = mwt::indoor_feedback::loadData(
        ytClientPtr, YT_DYNAMIC_TABLE_PATH, timeInterval);
    if (ytData.walls.empty() && ytData.pois.empty()) {
        return std::nullopt;
    }
    return ytData;
}

void
processYtData(
    mwt::indoor_feedback::YTData&& ytData,
    const mwc::ExtendedXmlDoc& configXml)
{
    // Parse YT data into tasks.
    mwc::PoolHolder viewTrunkPoolHolder(configXml, "view-trunk", "view-trunk");
    auto viewTrunkTxnHolder =
        viewTrunkPoolHolder.pool().masterReadOnlyTransaction();
    const auto newTasks = mwt::indoor_feedback::toTasks(*viewTrunkTxnHolder, std::move(ytData));

    // Upload tasks to social DB.
    mwc::PoolHolder socialPoolHolder{configXml, "social", "grinder"};
    auto socialTxnHolder = socialPoolHolder.pool().masterWriteableTransaction();
    mwt::indoor_feedback::uploadTasks(*socialTxnHolder, newTasks);

    socialTxnHolder->commit();
}

std::unique_ptr<mwc::ExtendedXmlDoc> loadConfig(
    const maps::cmdline::Option<std::string>& configPath)
{
    if (configPath.defined()) {
        return std::make_unique<mwc::ExtendedXmlDoc>(configPath);
    } else {
        return mwc::loadDefaultConfig();
    }
}

mwt::TimeIntervalMs getTimeInterval(
    const maps::cmdline::Option<size_t>& fromTime,
    const maps::cmdline::Option<size_t>& tillTime,
    pqxx::transaction_base& coreTxn)
{
    if (fromTime.defined() && tillTime.defined()) {
        return {fromTime, tillTime};
    } else if (!fromTime.defined() && !tillTime.defined()) {
        return {mwt::lastProcessedTimeMs(coreTxn, WATERMARK_TABLE) + 1,
            mwt::currentTimeUnixMs()};
    } else {
        REQUIRE(false, "Time interval is partially specified");
    }
}

using ErrorMessage = std::string;
[[nodiscard]] std::optional<ErrorMessage> doWork(
    const maps::cmdline::Option<size_t>& fromTime,
    const maps::cmdline::Option<size_t>& tillTime,
    const mwc::ExtendedXmlDoc& configXml)
{
    try {
        mwc::PoolHolder corePoolHolder{configXml, "core", "grinder"};
        maps::pgp3utils::PgAdvisoryXactMutex dbCoreLocker(
            corePoolHolder.pool(),
            static_cast<int64_t>(maps::wiki::common::AdvisoryLockIds::INDOOR_FEEDBACK));
        if (!dbCoreLocker.try_lock()) {
            INFO() << "Database is already locked. Task interrupted.";
            return std::nullopt;
        }

        auto& coreTxn = dbCoreLocker.writableTxn();
        const auto timeInterval = getTimeInterval(fromTime, tillTime, coreTxn);
        INFO() << "Processing data for time interval: ["
               << timeInterval.fromTime << ','
               << timeInterval.tillTime << ')';

        auto ytData = fetchDataFromYT(timeInterval);

        if (ytData.has_value()) {
            INFO() << "Fetched " << ytData->pois.size() << " pois";
            INFO() << "Fetched " << ytData->walls.size() << " walls";
            const auto maxTimeStamp = ytData->maxTimeStamp;

            processYtData(std::move(ytData.value()), configXml);

            if (!fromTime.defined() && !tillTime.defined()) {
                INFO() << "Setting last processed time to " << maxTimeStamp;
                mwt::setLastProcessedTimeMs(
                    maxTimeStamp, coreTxn, WATERMARK_TABLE);
                coreTxn.commit();
            }
        } else {
            INFO() << "No data to process. Exiting...";
        }
    } catch (const maps::Exception& e) {
        return (std::stringstream() << e).str();
    } catch (const std::exception& e) {
        return e.what();
    } catch (...) {
        return "unknown exception";
    }
    return std::nullopt;
}

} // unnamed namespace

int main(int argc, char* argv[]) try
{
    // Parse cmd args
    maps::cmdline::Parser parser;

    auto configPath = parser.string("config-path").
        help("Path to configuration file, where path to "
             "social service exists");

    auto fromTime = parser.size_t("from-time").
        help("Util will process data from YT\n"
             "from submit time interval ['from-time', 'till-time').\n"
             "Goes in conjunction with 'till-time'. In unix ms.");
    auto tillTime = parser.size_t("till-time").
        help("Goes in conjunction with 'from-time'");

    auto syslogTag = parser.string("syslog-tag")
        .help("Redirect log output to syslog with given tag");
    auto statusDir = parser.string("status-dir")
        .help("Path to status dir");

    parser.parse(argc, argv);

    if (syslogTag.defined()) {
        maps::log8::setBackend(maps::log8::toSyslog(syslogTag));
    }

    maps::wiki::tasks::StatusWriter statusWriter(std::nullopt);
    if (statusDir.defined()) {
        statusWriter.reinit(statusDir, MODULE_NAME + ".status");
    }

    const auto configXml = loadConfig(configPath);
    const auto error = doWork(fromTime, tillTime, *configXml);

    int retVal = EXIT_SUCCESS;
    if (error.has_value()) {
        ERROR() << error.value();
        statusWriter.err(error.value());
        retVal = EXIT_FAILURE;
    }
    statusWriter.flush();

    return retVal;
} catch (const maps::Exception& e) {
    FATAL() << e;
    return EXIT_FAILURE;
} catch (const std::exception& e) {
    FATAL() << e.what();
    return EXIT_FAILURE;
}
