#include <maps/wikimap/mapspro/services/tasks_feedback/src/releases_notification_worker/lib/releases_notification.h>
#include <maps/wikimap/mapspro/services/tasks_feedback/src/releases_notification_worker/lib/common.h>

#include <maps/wikimap/mapspro/libs/sender/include/config.h>

#include <maps/tools/grinder/worker/include/api.h>

#include <maps/libs/cmdline/include/cmdline.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/mongo/include/init.h>
#include <maps/libs/pgpool/include/pgpool3.h>
#include <yandex/maps/wiki/common/geom.h>
#include <yandex/maps/wiki/common/default_config.h>
#include <yandex/maps/wiki/common/extended_xml_doc.h>
#include <yandex/maps/wiki/common/pgpool3_helpers.h>
#include <yandex/maps/wiki/tasks/task_logger.h>
#include <yandex/maps/wiki/revision/common.h>
#include <yandex/maps/wiki/tasks/tasks.h>
#include <maps/libs/common/include/environment.h>
#include <maps/libs/locale/include/convert.h>

#include <memory>
#include <string>

#include <boost/lexical_cast.hpp>


namespace wiki = maps::wiki;
namespace common = wiki::common;
namespace revision = wiki::revision;
namespace rn = wiki::releases_notification;
namespace tasks = wiki::tasks;
namespace worker = maps::grinder::worker;
namespace locale = maps::locale;
namespace sender = maps::wiki::sender;
namespace json = maps::json;

namespace {

const std::string TASK_TYPE = "releases_notification";

const size_t WORKER_CONCURRENCY = 1;

} // anonymous namespace

std::unique_ptr<sender::BaseGateway>
initSenderGateway(const sender::Config& config, const rn::TaskParams& taskParams)
{
    if (taskParams.mode() == rn::Mode::Dry) {
        return std::make_unique<sender::DryGateway>(
            config.endPoint,
            config.credentials,
            rn::defaultRetryPolicy()
        );
    } else {
        return std::make_unique<sender::Gateway>(
            config.endPoint,
            config.credentials,
            rn::defaultRetryPolicy()
        );
    }
}

sender::LocalizedCampaign
initLocalizedCampaign(const sender::Config& config, const rn::TaskParams& taskParams)
{
    if (taskParams.mode() == rn::Mode::Dry) {
        sender::LocalizedCampaign campaign;
        campaign.name = "stub-campaign";
        campaign.slugs = sender::LocalizedString(
            sender::LangToString({{rn::DEFAULT_LANG, "stub-campaign-slug"}})
        );
        return campaign;
    }

    auto campaignName = boost::lexical_cast<std::string>(taskParams.releaseType());
    return config.campaignSlugs.getLocalizedByName(campaignName);
}

int main(int argc, char** argv)
{
    try {
        maps::cmdline::Parser parser;

        auto syslogTag = parser.string("syslog-tag").help(
            "redirect log output to syslog with given tag");
        auto workerConfig = parser.file("config").help(
            "path to services configuration");
        auto senderConfigArg = parser.file("sender-config").help(
            "path to sender configuration");
        auto grinderConfig = parser.file("grinder-config").help(
            "path to grinder configuration");

        parser.parse(argc, argv);

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

        maps::mongo::init();

        std::unique_ptr<common::ExtendedXmlDoc> configDocPtr;
        if (workerConfig.defined()) {
            configDocPtr.reset(new common::ExtendedXmlDoc(workerConfig));
        } else {
            configDocPtr = common::loadDefaultConfig();
        }

        sender::Config senderConfig = [&]() {
            return senderConfigArg.defined()
                ? sender::Config::fromJson(json::Value::fromFile(senderConfigArg))
                : sender::loadDefaultConfig();
        }();

        worker::Options options;
        if (grinderConfig.defined()) {
            options.setConfigLocation(grinderConfig);
        }

        options.setConcurrencyLevel(WORKER_CONCURRENCY);
        options.on(TASK_TYPE, [&configDocPtr, &senderConfig](const worker::Task& task)
        {
            INFO() << "Received releases notification task, id: " << task.id();

            common::PoolHolder longReadPoolHolder(
                *configDocPtr, "long-read", "long-read");
            common::PoolHolder socialPoolHolder(
                *configDocPtr, "social", "grinder");
            rn::PgPools pgPools { longReadPoolHolder.pool(), socialPoolHolder.pool() };

            auto dbTaskId = task.args()["task-id"].as<revision::DBID>();

            tasks::TaskPgLogger logger(pgPools.longReadPool, dbTaskId);
            bool frozen = tasks::isFrozen(*pgPools.longReadPool.masterReadOnlyTransaction(), dbTaskId);
            logger.logInfo() << "Task " << (frozen ? "resumed" : "started")
                             << ". Grinder task id: " << task.id();

            rn::TaskParams params(*pgPools.longReadPool.masterReadOnlyTransaction(), dbTaskId);

            if (params.mode() == rn::Mode::Real) {
                auto env = maps::common::getYandexEnvironment();
                if (env != maps::common::Environment::Stable) {
                    logger.logError() << "Forbidden real mode for environment: " << env;
                    logger.logInfo() << "Task cancelled";
                    throw worker::TaskCanceledException();
                }
            }

            if (not frozen) {
                rn::collectEmailsForNotification(
                    *configDocPtr,
                    senderConfig,
                    params,
                    pgPools,
                    logger);
            }

            if (task.isCanceled()) {
                WARN() << "Task " << task.id() << ": cancelled before sending";
                logger.logInfo() << "Task cancelled";
                throw worker::TaskCanceledException();
            }
            logger.logInfo() << "Notification started";

            auto senderGateway = initSenderGateway(senderConfig, params);
            auto localizedCampaign = initLocalizedCampaign(senderConfig, params);

            rn::notifyUsers(pgPools, dbTaskId, *senderGateway, localizedCampaign, logger);

            if (frozen) {
                auto txnCore = pgPools.longReadPool.masterWriteableTransaction();
                tasks::unfreezeTask(*txnCore, dbTaskId);
                txnCore->commit();
            }

            if (task.isCanceled()) {
                WARN() << "Task " << task.id() << ": cancelled after sending";
                logger.logWarn() << "Task cancelled";
                throw worker::TaskCanceledException();
            } else {
                INFO() << "Task " << task.id() << ": completed succesfully";
                logger.logInfo() << "Task completed succesfully";
            }
        });

        worker::run(std::move(options));

        return EXIT_SUCCESS;
    } catch (const maps::Exception& ex) {
        FATAL() << "Worker failed: " << ex;
        return EXIT_FAILURE;
    } catch (const std::exception& ex) {
        FATAL() << "Worker failed: " << ex.what();
        return EXIT_FAILURE;
    }
}
