#include <maps/wikimap/ugc/prepare_ammo/contributions/lib/prepare_ammo.h>

#include <maps/wikimap/feedback/api/src/libs/feedback_task_query_builder/select_query.h>
#include <maps/wikimap/feedback/api/src/libs/ugc/proto_helpers.h>
#include <yandex/maps/proto/common2/image.pb.h>
#include <maps/libs/log8/include/log8.h>
#include <fstream>

namespace maps::wiki::ugc::ammo {

namespace common2 = yandex::maps::proto::common2;

namespace {

// ya tool tvmknife unittest service --src 2020773 --dst 2025132
// from https://abc.yandex-team.ru/services/maps-core-feedback-api
// to https://abc.yandex-team.ru/services/maps-core-ugc-backoffice
// in testing
const std::string IMMORTAL_TICKET =
    "3:serv:CBAQ__________9_IggIpat7EKzNew:GgD_Xmc3CqWII6_mPxDZTwvyr_A8QGOWhUR"
    "cs5gOTOQs2LyxjL1hM0zZ-8ge3UQkoRV5dGIqspyCueELStJRYbrngDt2KngiDc-Tfs56-3yp"
    "DruR8C0nP0VHGR1SbvT7-2QHWD5cNUtjdO8AhP5Ad7FiBo6Z1c5gP45Wk9jLVGw";

const std::vector<fb::avatars::ImageSize> SIZES {
    {"XXXS", 1, 1},
    {"XXS", 10, 10},
    {"XS", 20, 20},
    {"S", 30, 30},
    {"M", 50, 50},
    {"L", 100, 100},
    {"XL", 500, 500},
    {"XXL", 1000, 1000},
    {"XXXL", 1500, 1500}
};

template <class ContributionData>
void addPhotos(
    ContributionData& data,
    size_t photosCount)
{
    for (size_t i = 0; i < photosCount; ++i) {
        common2::Image pbImage;
        auto image = data.add_photos();
        image->set_url_template(TString{"http://fake_avatars_url/photoId_" + std::to_string(i) + "/%"});
        for (const auto& size : SIZES) {
            auto imageSize = image->add_size();
            imageSize->set_size(TString(size.alias));
            imageSize->set_width(size.width);
            imageSize->set_height(size.height);
        }
    }
}

template <class ContributionData>
void fillCommonData(
    ContributionData& data,
    const fb::FeedbackTask& task)
{
    *data.mutable_id() = task.id.value();
    *data.mutable_title() = std::string{toString(task.originalTask.questionId())} + "."
        + std::string{toString(task.originalTask.answerId())};
    *data.mutable_description() = "Fake address for fbtask " + task.id.value();
    fb::ugc::setStatus<ContributionData>(data, task.status);
    if (task.originalTask.message()) {
        *data.mutable_user_message() = "User left some message";
    }
}

} // namespace

namespace fbqb = fb::feedback_task_query_builder;

std::vector<fb::FeedbackTask> loadTasks(
    pqxx::transaction_base& txn,
    size_t offset,
    size_t limit)
{
    return fbqb::SelectQuery(fbqb::WhereConditions().uidNotNull())
        .orderBy(fbqb::SelectOrder::UpdatedAtDesc)
        .limit(limit)
        .offset(offset)
        .exec(txn);
}

proto::backoffice::ModifyContributions makeInsertContribution(const fb::FeedbackTask& task)
{
    proto::backoffice::ModifyContributions modifyContributions;
    auto* modifyContribution = modifyContributions.add_modify_contribution();
    auto* upsert = modifyContribution->mutable_upsert();
    *upsert->mutable_id() = "fb:" + task.id.value();
    auto* item = upsert->mutable_contribution();
    // Use createdAt instead of updatedAt because we want to order
    // contributions in feed by creating moment
    item->set_timestamp(maps::chrono::convertToUnixTime(task.createdAt));
    auto& langToMetadata = *item->mutable_lang_to_metadata();

    proto::contribution::ContributionMetadata metadata;

    switch (task.originalTask.formId()) {
        case fb::FormId::Toponym: {
            proto::contributions::feedback::BaseMapContribution baseMap;

            baseMap.set_type(fb::ugc::getToponymType(task.originalTask));
            fb::ugc::setPoint(baseMap, task);
            addPhotos(baseMap, task.originalTask.attachedPhotos().size());

            fillCommonData(baseMap, task);
            *metadata.mutable_base_map() = baseMap;
            break;
        }
        case fb::FormId::Organization: {
            proto::contributions::feedback::OrganizationContribution org;
            fb::ugc::setPoint(org, task);
            if (task.originalTask.objectUri()) {
                *org.mutable_uri() = *task.originalTask.objectUri();
            }
            fillCommonData(org, task);
            *metadata.mutable_organization() = org;
            break;
        }
        case fb::FormId::Route: {
            proto::contributions::feedback::RouteContribution route;
            REQUIRE(
                task.originalTask.questionContext().hasField("route"),
                "Route question context must have 'route' field"
            );
            if (task.originalTask.questionContext()["route"].hasField("uri")) {
                *route.mutable_uri() = task.originalTask.questionContext()["route"]["uri"].as<std::string>();
            }
            *route.mutable_type() = fb::ugc::getRouteType(task.originalTask);
            fillCommonData(route, task);
            *metadata.mutable_route() = route;
            break;
        }
        case fb::FormId::Other: {
            REQUIRE(false, "FormId::Other not allowed in here");
        }
    }
    langToMetadata[task.originalTask.metadata().locale.c_str()] = metadata;

    return modifyContributions;
}

proto::backoffice::ModifyContributions makeDeleteContribution(const fb::FeedbackTask& task)
{
    proto::backoffice::ModifyContributions modifyContributions;
    auto* modifyContribution = modifyContributions.add_modify_contribution();
    modifyContribution->mutable_delete_()->set_id(TString{"fb:" + task.id.value()});
    return modifyContributions;
}

void writeAmmo(
    std::ostream& out,
    const proto::backoffice::ModifyContributions& contribution,
    uint64_t uid)
{
    static const std::string HEADERS =
        "X-Ya-Service-Ticket: " + IMMORTAL_TICKET + "\n"
        "Host: core-ugc-backoffice.maps.yandex.net\n"
        "Accept: */*\n"
        "User-Agent: tank\n"
        "Content-Type: application/octet-stream\n";
    TString data;
    REQUIRE(
        contribution.SerializeToString(&data),
        "Problem occured while serializing proto data");
    std::string strData{data};
    std::string request =
        "PUT /v1/contributions/modify?uid=" + std::to_string(uid) + " HTTP/1.0\r\n" +
        HEADERS + "Content-Length: " + std::to_string(contribution.ByteSizeLong()) + "\r\n\r\n" +
        strData + "\r\n";

    out << request.size() << " contribution_modify\n" << request;
}

void makeAmmo(
    pgpool3::Pool& pool,
    const std::string& createAmmoPath,
    const std::string& deleteAmmoPath,
    size_t ammoOffset,
    size_t ammoSize)
{
    auto txn = pool.slaveTransaction();
    INFO() << "Loading tasks...";
    auto tasks = loadTasks(*txn, ammoOffset, ammoSize);
    std::ofstream createOut(createAmmoPath, std::ios::binary);
    std::ofstream deleteOut(deleteAmmoPath, std::ios::binary);
    INFO() << "Writing modify/delete requests...";
    uint64_t fakeUid = 1;
    for (const fb::FeedbackTask& task : tasks) {
        auto insertContribution = makeInsertContribution(task);
        auto deleteContribution = makeDeleteContribution(task);
        writeAmmo(createOut, insertContribution, fakeUid);
        writeAmmo(deleteOut, deleteContribution, fakeUid);
        ++fakeUid;
    }
}

} // maps::wiki::ugc::ammo
