#include <maps/wikimap/ugc/account/src/lib/assignments.h>
#include <maps/wikimap/ugc/account/src/lib/contributions.h>
#include <maps/wikimap/ugc/account/src/lib/globals.h>
#include <maps/wikimap/ugc/account/src/lib/params.h>
#include <maps/wikimap/ugc/account/src/lib/gdpr/takeout.h>

#include <maps/wikimap/ugc/libs/common/config.h>
#include <maps/wikimap/ugc/libs/common/dbqueries.h>
#include <maps/wikimap/ugc/libs/common/options.h>

#include <maps/infra/yacare/include/limit_rate.h>
#include <maps/infra/yacare/include/params.h>
#include <maps/infra/yacare/include/params/tvm.h>
#include <maps/infra/yacare/include/tvm.h>
#include <maps/infra/yacare/include/yacare.h>
#include <maps/libs/common/include/exception.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/pgpool3utils/include/yandex/maps/pgpool3utils/dynamic_pool_holder.h>


using namespace maps::wiki::ugc;
using namespace maps::wiki::ugc::account;

namespace {

const size_t THREADS = 64;
const size_t BACKLOG = 1024;
yacare::ThreadPool MAIN_THREAD_POOL("mainThreadPool", THREADS, BACKLOG);

const std::string& CONFIG_PATH = "/etc/yandex/maps/ugc_account/ugc_account.conf";

std::unique_ptr<Globals> g_globals;

Globals& globals()
{
    REQUIRE(g_globals, "Globals are not initialized");
    return *g_globals;
}

const std::set<MetadataId> ASSIGNMENTS_REQUIRE_GEOMETRY = {
    MetadataId{proto::assignment::AssignmentMetadata::AssignmentCase::kPedestrianAssignment}
};

} // namespace

// The number of seconds before the shutdown to serve requests while not
// responding to /ping.
YCR_OPTIONS.shutdown().grace_period() = 10;


yacare::VirtualHost yacareVhost {
    yacare::VirtualHost::SLB { "core-ugc-account" }
};

const std::string TVM_ALIAS = "maps-core-ugc-account";

YCR_SET_DEFAULT(
    yacareVhost,
    yacare::Tvm2ServiceCheck(TVM_ALIAS),
    yacare::LimitRate().resource("maps_core_ugc_account")
);

YCR_USE(MAIN_THREAD_POOL) {

YCR_RESPOND_TO(
    "GET /v1/assignments/find",
    userId,
    metadata_ids,
    paging,
    status,
    lang,
    request_id = ""
) {
    Uid uid{userId};
    INFO() << "Request from " << uid;
    auto txn = globals().pools.at(uid).slaveTransaction();
    response << findAssignments(*txn, uid, status, metadata_ids, paging, Lang{lang}.locale());
}

YCR_RESPOND_TO(
    "GET /v1/assignments/find_in_bbox",
    userId,
    metadata_ids,
    bbox,
    status,
    lang,
    limit,
    request_id = ""
) {
    Uid uid{userId};
    INFO() << "Request from " << uid;
    REQUIRE(
        limit > 0,
        yacare::errors::BadRequest() << "Bad limit value: " << limit << "; limit must be > 0"
    );
    REQUIRE(
        !bbox.isDegenerate(),
        yacare::errors::BadRequest() << "Cannot process degenerate bbox."
    );
    for (const MetadataId& id : metadata_ids) {
        REQUIRE(
            ASSIGNMENTS_REQUIRE_GEOMETRY.contains(id),
            yacare::errors::BadRequest() << "Cannot find in bbox assignment with metadata_id " << id
        );
    }
    auto txn = globals().pools.at(uid).slaveTransaction();
    response << findAssignments(*txn, uid, status, metadata_ids, bbox, limit, Lang{lang}.locale());
}

YCR_RESPOND_TO(
    "PUT /v1/assignments/skip",
    userId,
    task_id,
    request_id = ""
) {
    Uid uid{userId};
    INFO() << "Request from " << uid;
    auto txn = globals().pools.at(uid).masterWriteableTransaction();
    skipAssignment(*txn, uid, task_id);
    finishTxn(*txn, globals().dryRun());
}

YCR_RESPOND_TO(
    "GET /v1/contributions/find",
    userId,
    metadata_ids,
    paging,
    lang,
    request_id = ""
) {
    Uid uid{userId};
    INFO() << "Request from " << uid;
    auto txn = globals().pools.at(uid).slaveTransaction();
    response << findContributions(*txn, uid, metadata_ids, paging, Lang{lang}.locale());
}

YCR_RESPOND_TO(
    "GET /v1/contributions/stat",
    userId,
    metadata_ids,
    request_id = ""
) {
    Uid uid{userId};
    INFO() << "Request from " << uid;
    auto txn = globals().pools.at(uid).slaveTransaction();
    response << getContributionsStat(*txn, uid, metadata_ids);
}

YCR_RESPOND_TO("GET /v1/takeout/status/", tvmId, userId, request_id)
try {
    Uid uid{userId};
    INFO() << "Request from " << uid;
    auto txn = globals().pools.at(uid).slaveTransaction();
    auto takeoutStatus = gdpr::getTakeoutStatus(*txn, uid);
    response << gdpr::makeTakeoutStatusResponseJson(takeoutStatus);
} catch (const maps::Exception& ex) {
    ERROR() << "Error on get takeout status "
        "(uid:" << userId << ", requestId:" << request_id << "): " << ex;
    response << gdpr::makeTakeoutErrorJson(ex.what());
} catch (const std::exception& ex) {
    ERROR() << "Error on get takeout status "
        "(uid:" << userId << ", requestId:" << request_id << "): " << ex.what();
    response << gdpr::makeTakeoutErrorJson(ex.what());
}

YCR_RESPOND_TO("POST /v1/takeout/delete/", tvmId, userId, request_id)
try {
    gdpr::checkCategoryId(request.body());

    Uid uid{userId};
    INFO() << "Request from " << uid;
    auto txn = globals().pools.at(uid).masterWriteableTransaction();

    if (!gdpr::scheduleUserDataRemoval(*txn, uid, gdpr::RequestId(request_id))) {
        response << gdpr::makeTakeoutErrorJson("Deletion already in progress", "in_progress");
        return;
    }

    finishTxn(*txn, globals().dryRun());

    response << YCR_JSON(obj) {
        obj["status"] << "ok";
    };
} catch (const maps::Exception& ex) {
    ERROR() << "Error on post takeout delete "
        "(uid:" << userId << ", requestId:" << request_id << "): " << ex;
    response << gdpr::makeTakeoutErrorJson(ex.what());
} catch (const std::exception& ex) {
    ERROR() << "Error on post takeout delete "
        "(uid:" << userId << ", requestId:" << request_id << "): " << ex.what();
    response << gdpr::makeTakeoutErrorJson(ex.what());
}

} // YCR_USE MAIN_THREAD_POOL

YCR_MAIN(argc, argv) {
    try {
        auto options = parseOptions(argc, argv, CONFIG_PATH);
        g_globals = Globals::create(options.confPath, options.dryRun);

        INFO() << "Enable TVM suppport...";
        auto tvmtoolSettings =
            maps::auth::TvmtoolSettings().selectClientAlias(TVM_ALIAS);
        yacare::tvm::configureUserAuth(tvmtoolSettings);

        INFO() << "Running yacare app...";
        yacare::run();
        g_globals.reset();

        return EXIT_SUCCESS;
    } catch (const maps::Exception& e) {
        ERROR() << "maps::Exception: " << e;
    } catch (const std::exception& e) {
        ERROR() << "std::exception: " << e.what();
    }

    return EXIT_FAILURE;
}
