#include <maps/wikimap/mapspro/services/tasks_realtime/src/publication_check_worker/lib/branch_events.h>
#include <maps/wikimap/mapspro/services/tasks_realtime/src/publication_check_worker/lib/export.h>

#include <yandex/maps/wiki/unittest/arcadia.h>

#include <maps/libs/common/include/file_utils.h>
#include <maps/libs/pgpool/include/pgpool3.h>

#include <library/cpp/testing/unittest/env.h>
#include <library/cpp/testing/unittest/registar.h>

using namespace maps;
using namespace maps::wiki;

namespace {

constexpr revision::UserID TEST_USER = 1;
constexpr BranchId TEST_BRANCH_1 = 1;
constexpr BranchId TEST_BRANCH_2 = 2;
constexpr BranchId TEST_BRANCH_3 = 3;
constexpr BranchId TEST_BRANCH_UNEXISTENT = 123;
const std::string TEST_REGION = "test_region";

void prepareBranches(maps::pgpool3::Pool& pool)
{
    auto work = pool.masterWriteableTransaction();
    revision::BranchManager branchManager(*work);

    auto branch = branchManager.createStable(TEST_USER, {
        { EVENT_EXPORTED + ":" + TEST_REGION, "2018-04-02 09:08:02+00:00" },
        { "some_other_attribute", "1" }
    });
    UNIT_ASSERT_VALUES_EQUAL(branch.id(), TEST_BRANCH_1);
    branch.finish(*work, TEST_USER);

    auto branch2 = branchManager.createStable(TEST_USER, {
        { "some_attribute", "1" }
    });
    UNIT_ASSERT_VALUES_EQUAL(branch2.id(), TEST_BRANCH_2);
    branch2.finish(*work, TEST_USER);

    auto branch3 = branchManager.createStable(TEST_USER, {
        { "some_attribute", "1" }
    });
    UNIT_ASSERT_VALUES_EQUAL(branch3.id(), TEST_BRANCH_3);

    work->commit();
}

template<typename Container>
void compareEvents(
    const Container& lhs,
    const Container& rhs)
{
    std::list<typename Container::value_type> lhsList(lhs.begin(), lhs.end());
    std::list<typename Container::value_type> rhsList(rhs.begin(), rhs.end());

    lhsList.sort();
    rhsList.sort();

    auto l = lhsList.begin();
    auto r = rhsList.begin();
    while (l != lhsList.end() && r != rhsList.end()) {
        UNIT_ASSERT_STRINGS_EQUAL(*l, *r);
        ++l;
        ++r;
    }
    UNIT_ASSERT(l == lhsList.end());
    UNIT_ASSERT(r == rhsList.end());
}

} // namespace

Y_UNIT_TEST_SUITE_F(test_publication_check_worker, unittest::ArcadiaDbFixture)
{

Y_UNIT_TEST(test_branch_loading)
{
    {
        auto work = pool().masterReadOnlyTransaction();
        BranchStore branchStore(*work);
        UNIT_ASSERT(branchStore.empty());
    }

    prepareBranches(pool());

    {
        auto work = pool().masterReadOnlyTransaction();
        BranchStore branchStore(*work);

        const auto& branchToEvents = branchStore.branchToEvents();
        UNIT_ASSERT_VALUES_EQUAL(branchToEvents.size(), 3);

        auto it = branchToEvents.begin();
        UNIT_ASSERT_VALUES_EQUAL(it->first, TEST_BRANCH_1);
        compareEvents(it->second, { EVENT_EXPORTED + ":" + TEST_REGION });

        ++it;
        UNIT_ASSERT_VALUES_EQUAL(it->first, TEST_BRANCH_2);
        compareEvents(it->second, {});

        ++it;
        UNIT_ASSERT_VALUES_EQUAL(it->first, TEST_BRANCH_3);
        compareEvents(it->second, {});
    }
}

Y_UNIT_TEST(test_events_merge_and_writing)
{
    prepareBranches(pool());

    {
        auto work = pool().masterWriteableTransaction();
        BranchStore branchStore(*work);
        UNIT_ASSERT(!branchStore.empty());

        EventsHolder eventsHolder;
        eventsHolder.addEvent(
            EVENT_DEPLOYED_RENDERER,
            TEST_REGION,
            TEST_BRANCH_2,
            chrono::TimePoint::clock::now());
        eventsHolder.addEvent(
            EVENT_EXPORTED,
            TEST_REGION,
            TEST_BRANCH_3,
            chrono::TimePoint::clock::now());

        branchStore.mergeEvents(eventsHolder);
        branchStore.writeAttributes(*work);
        work->commit();

        const auto publishedRegions = branchStore.publishedRegions();
        UNIT_ASSERT_VALUES_EQUAL(publishedRegions.size(), 1);
        const auto& pair = *publishedRegions.begin();
        UNIT_ASSERT_VALUES_EQUAL(pair.first, TEST_REGION);
        UNIT_ASSERT_VALUES_EQUAL(pair.second.size(), 1);
        UNIT_ASSERT_VALUES_EQUAL(*pair.second.begin(), TEST_BRANCH_2);
    }

    {
        auto work = pool().masterReadOnlyTransaction();
        BranchStore branchStore(*work);

        const auto& branchToEvents = branchStore.branchToEvents();
        UNIT_ASSERT_VALUES_EQUAL(branchToEvents.size(), 3);

        auto it = branchToEvents.begin();
        UNIT_ASSERT_VALUES_EQUAL(it->first, TEST_BRANCH_1);
        compareEvents(it->second, { EVENT_EXPORTED + ":" + TEST_REGION });

        ++it;
        UNIT_ASSERT_VALUES_EQUAL(it->first, TEST_BRANCH_2);
        compareEvents(it->second, { EVENT_EXPORTED + ":" + TEST_REGION, EVENT_DEPLOYED_RENDERER + ":" + TEST_REGION });

        ++it;
        UNIT_ASSERT_VALUES_EQUAL(it->first, TEST_BRANCH_3);
        compareEvents(it->second, { EVENT_EXPORTED + ":" + TEST_REGION });
    }
}

Y_UNIT_TEST(test_unexistent_branch_merge)
{
    prepareBranches(pool());

    auto work = pool().masterWriteableTransaction();
    BranchStore branchStore(*work);
    UNIT_ASSERT(!branchStore.empty());

    EventsHolder eventsHolder;
    eventsHolder.addEvent(
        EVENT_DEPLOYED_RENDERER,
        TEST_REGION,
        TEST_BRANCH_UNEXISTENT,
        chrono::TimePoint::clock::now());

    branchStore.mergeEvents(eventsHolder);

    UNIT_ASSERT(branchStore.attrsToMerge().empty());
}

Y_UNIT_TEST(test_export)
{
    {
        auto work = pool().masterWriteableTransaction();
        work->exec(
            "INSERT INTO service.export_task (id, commit_id, subset, branch_id) "
            "VALUES (1, 1, 'domain', " + std::to_string(TEST_BRANCH_1) + ")");
        work->exec(
            "INSERT INTO service.export_result (task_id, message, dataset_id) "
            "VALUES (1, '', 'some_dataset_id')");
        work->exec(
            "INSERT INTO mds_dataset.export_meta (id, status, created, subset, region) "
            "VALUES ('some_dataset_id', 0, to_timestamp(1522675011), 0, '" + TEST_REGION + "')");
        work->exec(
            "INSERT INTO mds_dataset.export_files (dataset_id, mds_group_id, mds_path, name, url, region) "
            "VALUES ('some_dataset_id', 'group_id', 'path', 'name', 'http://example.com', '" + TEST_REGION + "')");
        work->commit();
    }

    auto work = pool().masterReadOnlyTransaction();
    auto eventHolder = loadBranchExportedTimes(*work);

    const auto& data = eventHolder.data();
    UNIT_ASSERT_VALUES_EQUAL(data.size(), 1);
    auto it = data.begin();
    UNIT_ASSERT_STRINGS_EQUAL(it->first, EVENT_EXPORTED + ":" + TEST_REGION);
    const auto& branchToTime = it->second;
    UNIT_ASSERT_VALUES_EQUAL(branchToTime.size(), 1);
    UNIT_ASSERT_VALUES_EQUAL(branchToTime.begin()->first, TEST_BRANCH_1);
}

} //Y_UNIT_TEST_SUITE
