#include "params.h"

#include <maps/libs/chrono/include/days.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/common/include/exception.h>

#include <maps/wikimap/mapspro/libs/assessment/include/gateway.h>

#include <maps/wikimap/mapspro/services/tasks_social/src/assessment_sampler/lib/init.h>
#include <maps/wikimap/mapspro/services/tasks_social/src/assessment_sampler/lib/make_basic_sample.h>
#include <maps/wikimap/mapspro/services/tasks_social/src/assessment_sampler/lib/make_expert_sample.h>
#include <maps/wikimap/mapspro/services/tasks_social/src/assessment_sampler/lib/sample.h>

namespace chrono = maps::chrono;
namespace log8 = maps::log8;
namespace assessment = maps::wiki::assessment;

using namespace maps::wiki::assessment::sampler;

namespace {

void makeSample(
    pqxx::transaction_base& txn,
    assessment::Entity::Domain domain,
    assessment::Qualification qualification,
    chrono::TimePoint date,
    int days,
    size_t seed,
    StaffType staffType,
    HypothesesMode hypothesesMode,
    const std::string& sampleName,
    const std::optional<std::string>& workerConfigPath)
{
    ASSERT(
        hypothesesMode == HypothesesMode::No || domain == assessment::Entity::Domain::Feedback
    );

    const auto timepointMax = std::chrono::floor<chrono::Days>(date + chrono::Days(1));
    const auto timepointMin = timepointMax - chrono::Days(days);
    std::mt19937 rndGen{seed};

    switch (qualification) {
    case assessment::Qualification::Basic:
        return makeBasicSample(
            txn,
            domain,
            timepointMin,
            timepointMax,
            rndGen,
            staffType,
            hypothesesMode,
            sampleName,
            workerConfigPath);
        break;
    case assessment::Qualification::Expert:
        return makeExpertSample(
            txn,
            domain,
            timepointMin,
            timepointMax,
            rndGen,
            staffType,
            hypothesesMode,
            sampleName);
        break;
    default:
        throw maps::RuntimeError() << "Qualification '" << qualification << "' is not supported";
    }
}

} // namespace


int main(int argc, char** argv)
{
    try {
        const auto params = parseCommandLineParams(argc, argv);
        if (params.syslogTag) {
            log8::setBackend(log8::toSyslog(*params.syslogTag));
        }
        params.log(log8::Level::INFO);

        auto socialPoolHolder = initPoolHolder(SOCIAL_DB_ID, params.workerConfigPath);
        auto socialLocker = initSocialLocker(socialPoolHolder.pool());

        INFO() << "Locking social database...";
        socialLocker.lock();
        INFO() << "Lock acquired.";

        auto& txn = socialLocker.writableTxn();
        if (!params.allowDuplicateSample) {
            if (assessment::Gateway(txn).sampleExistsByName(params.name)) {
                INFO() << "Sample '" << params.name << "' already exists. Task interrupted.";
                return EXIT_SUCCESS;
            }
        }

        makeSample(
            txn,
            params.domain,
            params.qualification,
            params.date,
            params.days,
            params.seed,
            params.staffType,
            params.hypothesesMode,
            params.name,
            params.workerConfigPath
        );

        if (!params.dryRun) {
            socialLocker.writableTxn().commit();
        }

        INFO() << "Done";
        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;
    }
}
