#include <library/cpp/testing/common/env.h>
#include <library/cpp/testing/gtest/gtest.h>
#include <maps/libs/geolib/include/spatial_relation.h>
#include <maps/wikimap/mapspro/services/mrc/libs/fb/include/walk_objects_reader.h>
#include <maps/wikimap/mapspro/services/mrc/libs/fb/include/walk_objects_writer.h>

namespace maps::mrc::fb::tests {

namespace {

const auto DUMP_DIRECTORY = std::string{"."};
const auto SOURCE_ID = std::string{"source_id"};
const auto TIME = chrono::parseSqlDateTime("2021-07-01 12:00:00+03");

db::WalkObjects makeWalkObjects(size_t count)
{
    auto result = db::WalkObjects{};
    for (size_t i = 0; i < count; ++i) {
        result.emplace_back(i)
            .setDataset(db::Dataset::Walks)
            .setDeviceId(SOURCE_ID)
            .setCreatedAt(TIME + std::chrono::seconds(i))
            .setFeedbackType(db::WalkFeedbackType::Other)
            .setGeodeticGeometry(geolib3::Point2(i + 0.5, i + 0.5));
    }
    return result;
}

}  // anonymous namespace

TEST(walkObjects, readWrite)
{
    auto walkObjects = makeWalkObjects(10);
    auto writer = WalkObjectsWriter{};
    for (const auto& walkObject : walkObjects) {
        writer.add(walkObject);
    }
    writer.dump("version", DUMP_DIRECTORY);

    auto reader = WalkObjectsReader{DUMP_DIRECTORY};
    EXPECT_EQ(reader.walkObjectsNumber(), walkObjects.size());
    for (const auto& expect : walkObjects) {
        auto test = reader.posByWalkObjectId(expect.id());
        EXPECT_TRUE(test.has_value());
        EXPECT_EQ(test.value(),
                  std::get<geolib3::Point2>(expect.geodeticGeometry()));
    }
    EXPECT_NO_THROW(reader.id(reader.walkObjectsNumber() - 1));
    EXPECT_THROW(reader.id(reader.walkObjectsNumber()), maps::Exception);
    EXPECT_NO_THROW(reader.pos(reader.walkObjectsNumber() - 1));
    EXPECT_THROW(reader.pos(reader.walkObjectsNumber()), maps::Exception);
}

TEST(walkObjects, noPoint)
{
    auto walkObject = db::WalkObject{1}
                          .setDataset(db::Dataset::Walks)
                          .setDeviceId(SOURCE_ID)
                          .setCreatedAt(TIME)
                          .setFeedbackType(db::WalkFeedbackType::Other)
                          .setGeodeticGeometry(geolib3::Polyline2{});
    auto writer = WalkObjectsWriter{};
    EXPECT_THROW(writer.add(walkObject), maps::Exception);
}

TEST(walkObjects, rtree)
{
    auto walkObjects = makeWalkObjects(10);
    auto writer = WalkObjectsWriter{};
    for (const auto& walkObject : walkObjects) {
        writer.add(walkObject);
    }
    writer.dump("version", DUMP_DIRECTORY);

    auto reader = WalkObjectsReader{DUMP_DIRECTORY};
    auto intersects = [&](const geolib3::BoundingBox& box, fb_rtree::Id id) {
        auto pos = reader.posByWalkObjectId(id);
        EXPECT_TRUE(pos.has_value());
        return spatialRelation(box, pos.value(), geolib3::Intersects);
    };
    for (const auto& expect : walkObjects) {
        auto tests = reader.rtree().allIdsInWindow(
            std::get<geolib3::Point2>(expect.geodeticGeometry()).boundingBox(),
            intersects);
        EXPECT_EQ(std::distance(tests.begin(), tests.end()), 1);
        EXPECT_EQ(db::TId(*tests.begin()), expect.id());
    }
}

}  // namespace maps::mrc::fb::tests
