#include "helpers.h"

#include "../revision_meta/categories.h"
#include "../revision_meta/common.h"
#include "../revision_meta/snapshot.h"

#include <yandex/maps/wiki/revision/branch_manager.h>
#include <yandex/maps/wiki/revision/commit_manager.h>
#include <yandex/maps/wiki/revision/exception.h>
#include <yandex/maps/wiki/revision/revisionsgateway.h>

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

#include <boost/test/unit_test.hpp>

#include <memory>

using namespace maps;
using namespace maps::wiki;
using namespace maps::wiki::revision;
using namespace maps::wiki::contours::revision_meta;
using namespace maps::wiki::contours::revision_meta::test;

namespace {

unsigned char fromHex(char c)
{
    if (c >= 'A' && c <= 'F') {
        return c - 'A' + 10;
    }
    if (c >= 'a' && c <= 'f') {
        return c - 'a' + 10;
    }
    ASSERT(c >= '0' && c <= '9');
    return c - '0';
}

std::string hex2wkb(const std::string& str)
{
    ASSERT(!(str.size() & 1));
    ASSERT(str.size() >= 16);

    std::string result;
    const char *s = str.c_str();
    for (; *s; s += 2) {
        unsigned char value = (fromHex(*s) << 4) + fromHex(s[1]);
        result.push_back(value);
    }
    return result;
}

const std::map<TObjectId, test::ObjectData> snapshot_tests_data = {
    // first commit
    {10, {10, {{"cat:ad_el", "1"}}, hex2wkb("0102000020430D0000040000001CF8066ACA3C50413176A12382695C419ECA3D1BE13C50418BFF8AEC576A5C419B5A58EADB3D5041556738A8E7695C411CF8066ACA3C50413176A12382695C41")}},
    {100, {100, {{"cat:ad_fc", "1"}}}},
    {1000, {1000, AD_FC_EL_PART_REL_ATTRS, RelationData{200, 30}}},
    {1001, {1001, AD_FC_EL_PART_REL_ATTRS, RelationData{300, 20}}},
    {1002, {1002, AD_FC_EL_PART_REL_ATTRS, RelationData{101, 10}}},
    {10000, {10000, {{"cat:ad", "1"}}}},
    {100000, {100000, AD_AD_FC_PART_REL_ATTRS, RelationData{10000, 100}}},
    {100001, {100001, AD_AD_FC_PART_REL_ATTRS, RelationData{20000, 300}}},
    {100002, {100002, AD_AD_FC_PART_REL_ATTRS, RelationData{30000, 200}}},
    // second commit
    {200, {200, {{"cat:ad_fc", "1"}}}},
    {201, {201, {{"cat:ad_fc", "1"}}}},
    {202, {202, {{"cat:ad_fc", "1"}}}},
    {20, {20, {{"cat:ad_el", "1"}}, hex2wkb("0102000020430D0000040000001CF8066ACA3C50413176A12382695C419ECA3D1BE13C50418BFF8AEC576A5C419B5A58EADB3D5041556738A8E7695C411CF8066ACA3C50413176A12382695C41")}},
    {21, {21, {{"cat:ad_el", "1"}}, hex2wkb("0102000020430D0000040000001CF8066ACA3C50413176A12382695C419ECA3D1BE13C50418BFF8AEC576A5C419B5A58EADB3D5041556738A8E7695C411CF8066ACA3C50413176A12382695C41")}},
    {2000, {2000, AD_FC_EL_PART_REL_ATTRS, RelationData{300, 10}}},
    {2001, {2001, AD_FC_EL_PART_REL_ATTRS, RelationData{100, 30}}},
    {2002, {2002, AD_FC_EL_PART_REL_ATTRS, RelationData{100, 21}}},
    {2003, {2003, AD_FC_EL_PART_REL_ATTRS, RelationData{201, 20}}},
    {2004, {2004, AD_FC_EL_PART_REL_ATTRS, RelationData{202, 21}}},
    {20000, {20000, {{"cat:ad", "1"}}}},
    {200001, {200001, AD_AD_FC_PART_REL_ATTRS, RelationData{10000, 300}}},
    {200002, {200002, AD_AD_FC_PART_REL_ATTRS, RelationData{30000, 100}}},
    {200003, {200003, AD_AD_FC_PART_REL_ATTRS, RelationData{30000, 300}}},
    {200004, {200004, AD_AD_FC_PART_REL_ATTRS, RelationData{20000, 301}}},
    {200005, {200005, AD_AD_FC_PART_REL_ATTRS, RelationData{30000, 201}}},
    // third commit
    {3000, {3000, AD_FC_EL_PART_REL_ATTRS, RelationData{200, 10}}},
    {3001, {3001, AD_FC_EL_PART_REL_ATTRS, RelationData{100, 20}}},
    {3002, {3002, AD_FC_EL_PART_REL_ATTRS, RelationData{301, 30}}},
    {3003, {3003, AD_FC_EL_PART_REL_ATTRS, RelationData{302, 30}}},
    {300, {300, {{"cat:ad_fc", "1"}}}},
    {301, {301, {{"cat:ad_fc", "1"}}}},
    {302, {302, {{"cat:ad_fc", "1"}}}},
    {30, {30, {{"cat:ad_el", "1"}}, hex2wkb("0102000020430D0000040000001CF8066ACA3C50413176A12382695C419ECA3D1BE13C50418BFF8AEC576A5C419B5A58EADB3D5041556738A8E7695C411CF8066ACA3C50413176A12382695C41")}},
    {30000, {30000, {{"cat:ad", "1"}}}},
    {300001, {300001, AD_AD_FC_PART_REL_ATTRS, RelationData{10000, 200}}},
    {300002, {300002, AD_AD_FC_PART_REL_ATTRS, RelationData{20000, 100}}},
    {300003, {300003, AD_AD_FC_PART_REL_ATTRS, RelationData{20000, 200}}},
    {300004, {300004, AD_AD_FC_PART_REL_ATTRS, RelationData{30000, 202}}},
    {300005, {300005, AD_AD_FC_PART_REL_ATTRS, RelationData{20000, 302}}},
    {300006, {300006, AD_AD_FC_PART_REL_ATTRS, RelationData{30000, 301}}}
};

void
appendCommitObjects(std::vector<ObjectDataVector>& data, const TObjectIdSet& objectIds)
{
    ObjectDataVector commitObjects;
    for (auto id : objectIds) {
        commitObjects.push_back(snapshot_tests_data.at(id));
    }
    data.push_back(commitObjects);
}

} // namespace

BOOST_FIXTURE_TEST_SUITE(snapshot_tests, unittest::MapsproDbFixture)

BOOST_AUTO_TEST_CASE(test_master_slave_relation_in_different_commits)
{
    std::vector<ObjectDataVector> data;
    appendCommitObjects(data, {10, 100, 1000, 1001, 10000, 100001, 100002});
    appendCommitObjects(data, {20, 200, 2000, 2001, 20000, 200001, 200002});
    appendCommitObjects(data, {30, 300, 3000, 3001, 30000, 300001, 300002});
    fillDatabase(pool(), TRUNK_BRANCH_ID, TEST_UID, COMMIT_ATTRS, data);
    auto txn = pool().masterReadOnlyTransaction();

    {
        contours::revision_meta::Snapshot snapshot(
            1, *txn, TRUNK_BRANCH_ID, getCategories());

        snapshot.loadObjects({100, 10000});
        snapshot.loadObjects({200, 20000});
        snapshot.loadObjects({300, 30000});
        BOOST_CHECK_EQUAL(snapshot.objectRevision(100).commitId(), 1);
        BOOST_CHECK_EQUAL(snapshot.objectRevision(10000).commitId(), 1);
        BOOST_CHECK(snapshot.tryGetObjectRevision(200) == nullptr);
        BOOST_CHECK(snapshot.tryGetObjectRevision(20000) == nullptr);
        BOOST_CHECK(snapshot.tryGetObjectRevision(300) == nullptr);
        BOOST_CHECK(snapshot.tryGetObjectRevision(30000) == nullptr);

        snapshot.loadSlaves({100, 10000});
        checkObjectSlaves(snapshot, 100, {});
        checkObjectSlaves(snapshot, 10000, {});
    }
    {
        contours::revision_meta::Snapshot snapshot(
            2, *txn, TRUNK_BRANCH_ID, getCategories());

        snapshot.loadObjects({100, 10000});
        snapshot.loadObjects({200, 20000});
        snapshot.loadObjects({300, 30000});
        BOOST_CHECK_EQUAL(snapshot.objectRevision(100).commitId(), 1);
        BOOST_CHECK_EQUAL(snapshot.objectRevision(10000).commitId(), 1);
        BOOST_CHECK_EQUAL(snapshot.objectRevision(200).commitId(), 2);
        BOOST_CHECK_EQUAL(snapshot.objectRevision(20000).commitId(), 2);
        BOOST_CHECK(snapshot.tryGetObjectRevision(300) == nullptr);
        BOOST_CHECK(snapshot.tryGetObjectRevision(30000) == nullptr);

        snapshot.loadSlaves({100, 10000});
        checkObjectSlaves(snapshot, 100, {});
        checkObjectSlaves(snapshot, 10000, {});
        snapshot.loadSlaves({200, 20000});
        checkObjectSlaves(snapshot, 200, {});
        checkObjectSlaves(snapshot, 20000, {});
    }
    {
        contours::revision_meta::Snapshot snapshot(
            3, *txn, TRUNK_BRANCH_ID, getCategories());

        snapshot.loadObjects({100, 10000});
        snapshot.loadObjects({200, 20000});
        snapshot.loadObjects({300, 30000});
        BOOST_CHECK_EQUAL(snapshot.objectRevision(100).commitId(), 1);
        BOOST_CHECK_EQUAL(snapshot.objectRevision(10000).commitId(), 1);
        BOOST_CHECK_EQUAL(snapshot.objectRevision(200).commitId(), 2);
        BOOST_CHECK_EQUAL(snapshot.objectRevision(20000).commitId(), 2);
        BOOST_CHECK_EQUAL(snapshot.objectRevision(300).commitId(), 3);
        BOOST_CHECK_EQUAL(snapshot.objectRevision(30000).commitId(), 3);

        snapshot.loadSlaves({100, 10000});
        checkObjectSlaves(snapshot, 100, {20, 30});
        checkObjectSlaves(snapshot, 10000, {200, 300});
        snapshot.loadSlaves({200, 20000});
        checkObjectSlaves(snapshot, 200, {10, 30});
        checkObjectSlaves(snapshot, 20000, {100, 300});
        snapshot.loadSlaves({300, 30000});
        checkObjectSlaves(snapshot, 300, {10, 20});
        checkObjectSlaves(snapshot, 30000, {100, 200});

        snapshot.loadMasters({10, 100});
        checkObjectMasters(snapshot, 10, {200, 300});
        checkObjectMasters(snapshot, 100, {20000, 30000});
        snapshot.loadMasters({20, 200});
        checkObjectMasters(snapshot, 20, {100, 300});
        checkObjectMasters(snapshot, 200, {10000, 30000});
        snapshot.loadMasters({30, 300});
        checkObjectMasters(snapshot, 30, {100, 200});
        checkObjectMasters(snapshot, 300, {10000, 20000});
    }
}

BOOST_AUTO_TEST_CASE(test_loading)
{
    std::vector<ObjectDataVector> data;
    appendCommitObjects(data, {10, 100, 1000, 1001, 1002, 10000, 100000, 100001, 100002});
    appendCommitObjects(data, {20, 21, 200, 201, 202, 2000, 2001, 2002, 2003, 2004, 20000, 200001, 200002, 200003, 200004, 200005});
    appendCommitObjects(data, {30, 300, 301, 302, 3000, 3001, 3002, 3003, 30000, 300001, 300002, 300003, 300004, 300005, 300006});
    fillDatabase(pool(), TRUNK_BRANCH_ID, TEST_UID, COMMIT_ATTRS, data);

    auto txn = pool().masterReadOnlyTransaction();
    contours::revision_meta::Snapshot snapshot(
        3, *txn, TRUNK_BRANCH_ID, getCategories());

    snapshot.loadObjects({10000, 20000, 30000});

    BOOST_CHECK_EQUAL(snapshot.objectRevision(10000).commitId(), 1);
    BOOST_CHECK_EQUAL(snapshot.objectRevision(20000).commitId(), 2);
    BOOST_CHECK_EQUAL(snapshot.objectRevision(30000).commitId(), 3);

    snapshot.loadSlaves({30000});
    checkObjectSlaves(snapshot, 30000, {100, 200, 201, 202, 300, 301});

    snapshot.loadSlavesRecursive({20000});
    checkObjectSlaves(snapshot, 20000, {100, 200, 300, 301, 302});
    checkObjectSlaves(snapshot, 100, {20, 21, 30});
    checkObjectSlaves(snapshot, 200, {10, 30});
    checkObjectSlaves(snapshot, 300, {10, 20});
    checkObjectSlaves(snapshot, 301, {30});
    checkObjectSlaves(snapshot, 302, {30});

    snapshot.loadMastersRecursive({10});
    checkObjectMasters(snapshot, 10, {200, 300});

    snapshot.loadSlavesRecursive({10000});
    checkObjectSlaves(snapshot, 10000, {100, 200, 300});
}

BOOST_AUTO_TEST_CASE(test_contour_object_loading)
{
    std::vector<ObjectDataVector> data;
    appendCommitObjects(data, {10, 100, 1000, 1001, 1002, 10000, 100000, 100001, 100002});
    appendCommitObjects(data, {20, 21, 200, 201, 202, 2000, 2001, 2002, 2003, 2004, 20000, 200001, 200002, 200003, 200004, 200005});
    appendCommitObjects(data, {30, 300, 301, 302, 3000, 3001, 3002, 3003, 30000, 300001, 300002, 300003, 300004, 300005, 300006});
    fillDatabase(pool(), TRUNK_BRANCH_ID, TEST_UID, COMMIT_ATTRS, data);

    auto txn = pool().masterReadOnlyTransaction();
    contours::revision_meta::Snapshot snapshot(
        3, *txn, TRUNK_BRANCH_ID, getCategories());

    const TObjectId contourObjectId = 10000;
    snapshot.loadObjects({contourObjectId});
    snapshot.loadSlavesRecursive({contourObjectId});

    snapshot.existingObjectRevision(contourObjectId);
    const auto ringIds = snapshot.slaves(contourObjectId);
    BOOST_CHECK_GT(ringIds.size(), 0);
    for (auto ringId: ringIds) {
        snapshot.existingObjectRevision(ringId);
        const auto linestringIds = snapshot.slaves(ringId);
        BOOST_CHECK_GT(linestringIds.size(), 0);
        for (auto linestringId: linestringIds) {
            snapshot.existingObjectRevision(linestringId);
        };
    };
}

BOOST_AUTO_TEST_SUITE_END()
