#include <maps/libs/chrono/include/time_point.h>
#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/mongo/include/init.h>
#include <maps/tools/grinder/worker/include/api.h>
#include <maps/wikimap/mapspro/services/mrc/libs/config/include/config.h>
#include <maps/wikimap/mapspro/services/mrc/libs/ugc_event_logger/include/logger.h>
#include <maps/wikimap/mapspro/services/mrc/long_tasks/async_ride_correction/lib/utility.h>
#include <util/generic/yexception.h>
#include <chrono>

int main(int argc, char* argv[])
try {
    maps::cmdline::Parser parser;
    auto configPath = parser.string("config").help("path to configuration");
    auto secretVersion = parser.string("secret").help(
        "version for secrets from yav.yandex-team.ru");
    auto syslogTag = parser.string("syslog-tag")
                         .help("redirect log output to syslog with given tag");
    auto grinderConfigPath = parser.string("grinder-config")
                                 .help("path to grinder configuration file");
    auto ugcEventLogPath = parser.string("ugc-event-log")
        .help("path to ugc events log")
        .defaultValue("/var/log/yandex/maps/mrc/ugc_events/ugc_events.log");
    parser.parse(argc, argv);

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

    const std::chrono::seconds LOG_ROTATION_PERIOD(3600);
    maps::mrc::ugc_event_logger::Logger ugcEventLogger(ugcEventLogPath, LOG_ROTATION_PERIOD);

    auto config =
        maps::mrc::common::templateConfigFromCmdPath(secretVersion, configPath);

    maps::mongo::init();

    maps::grinder::worker::Options workerOpts;
    if (grinderConfigPath.defined()) {
        workerOpts.setConfigLocation(grinderConfigPath);
    }
    workerOpts
        .on(maps::mrc::ride_correction::GRINDER_WORKER_TYPE,
            [&config, &ugcEventLogger](const maps::grinder::worker::Task& task) {
                MAPS_LOG_THREAD_PREFIX_APPEND(task.id());
                auto userId =
                    task.args()[maps::mrc::ride_correction::USER_ID_FIELD]
                        .as<std::string>();
                INFO() << "userId: " << userId;
                auto sourceId =
                    task.args()[maps::mrc::ride_correction::SOURCE_ID_FIELD]
                        .as<std::string>();
                INFO() << "sourceId: " << sourceId;
                auto clientRideId = std::optional<std::string>{};
                if (task.args().hasField(
                        maps::mrc::ride_correction::CLIENT_RIDE_ID_FIELD)) {
                    clientRideId =
                        task.args()
                            [maps::mrc::ride_correction::CLIENT_RIDE_ID_FIELD]
                                .as<std::string>();
                    INFO() << "clientRideId: " << clientRideId.value();
                }
                auto startTime = maps::chrono::TimePoint{};
                startTime += std::chrono::seconds{
                    task.args()[maps::mrc::ride_correction::START_TIME_FIELD]
                        .as<uint64_t>()};
                INFO() << "startTime: "
                       << maps::chrono::formatSqlDateTime(startTime);
                auto endTime = maps::chrono::TimePoint{};
                endTime += std::chrono::seconds{
                    task.args()[maps::mrc::ride_correction::END_TIME_FIELD]
                        .as<uint64_t>()};
                INFO() << "endTime: "
                       << maps::chrono::formatSqlDateTime(endTime);
                auto showAuthorship = std::optional<bool>{};
                if (task.args().hasField(
                        maps::mrc::ride_correction::SHOW_AUTHORSHIP_FIELD)) {
                    showAuthorship =
                        task.args()
                            [maps::mrc::ride_correction::SHOW_AUTHORSHIP_FIELD]
                                .as<bool>();
                    INFO() << "showAuthorship: " << showAuthorship.value();
                }

                auto userIpField = task.args()[maps::mrc::ride_correction::USER_IP_FIELD];

                maps::mrc::ugc_event_logger::UserInfo userInfo{
                        .ip = userIpField.exists() ? userIpField.as<std::string>() : "",
                        .uid = userId
                };

                auto userPortField = task.args()[maps::mrc::ride_correction::USER_PORT_FIELD];
                if (userPortField.exists()) {
                    userInfo.port = userPortField.as<uint16_t>();
                }

                while (true) {
                    try {
                        maps::mrc::ride_correction::updateRides(
                            config
                                .makePoolHolder(
                                    maps::mrc::common::LONG_READ_DB_ID,
                                    maps::mrc::common::LONG_READ_POOL_ID)
                                .pool(),
                            userId,
                            sourceId,
                            clientRideId,
                            startTime,
                            endTime,
                            showAuthorship,
                            userInfo,
                            ugcEventLogger);
                        return;
                    }
                    catch (const maps::Exception& e) {
                        ERROR() << e;
                    }
                    catch (const pqxx::failure& e) {
                        ERROR() << e.what();
                    }
                    std::this_thread::sleep_for(std::chrono::seconds(60));
                }
            })
        .setConcurrencyLevel(5);
    maps::grinder::worker::run(workerOpts);
    return EXIT_SUCCESS;
}
catch (const maps::Exception& e) {
    FATAL() << "Worker failed: " << e;
    return EXIT_FAILURE;
}
catch (const std::exception& e) {
    FATAL() << "Worker failed: " << e.what();
    return EXIT_FAILURE;
}
