#include "common.h"
#include "fixture.h"
#include "mocks.h"

#include <maps/infra/yacare/include/test_utils.h>
#include <maps/infra/yacare/include/yacare.h>
#include <maps/libs/http/include/test_utils.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/wikimap/mapspro/services/mrc/browser/lib/configuration.h>
#include <maps/wikimap/mapspro/services/mrc/browser/lib/proto/track_chunk_descriptor.pb.h>
#include <maps/wikimap/mapspro/services/mrc/libs/blackbox_client/include/blackbox_client.h>
#include <yandex/maps/geolib3/sproto.h>
#include <yandex/maps/mrc/unittest/utils.h>
#include <yandex/maps/proto/mrcphoto/photo.sproto.h>
#include <yandex/maps/wiki/common/string_utils.h>
#include <yandex/maps/wiki/unittest/query_helpers.h>

#include <google/protobuf/text_format.h>

#include <boost/lexical_cast.hpp>

namespace maps::mrc::browser::tests {

namespace sproto = yandex::maps::sproto;
namespace proto = yandex::maps::proto;

namespace {

const auto SRC_A = std::string("A");
const auto SRC_B = std::string("B");
const auto SRC_C = std::string("C");
const auto SRC_D = std::string("D");
const auto TIME_0 = std::string("2019-09-16 11:22:00");
const auto TIME_1 = std::string("2019-10-16 11:22:00");
const auto TIME_2 = std::string("2019-11-16 11:22:00");
const auto TIME_3 = std::string("2019-12-16 11:22:00");
const auto TIME_4 = std::string("2019-08-15 11:22:00");
const auto MDS = mds::Key("groupId", "path");

const geolib3::Point2 POINTS[] = {
    // Automotive points
    {37.569592, 55.791617}, // 0
    {37.569605, 55.791512}, // 1
    {37.569563, 55.791444}, // 2
    {37.569588, 55.791293}, // 3
    {37.569552, 55.791215}, // 4

    {37.569239, 55.791547}, // 5
    {37.569406, 55.791476}, // 6
    {37.569443, 55.791461}, // 7
    {37.569545, 55.791383}, // 8

    {37.569972, 55.791634}, // 9
    {37.569569, 55.791831}, // 10
    {37.569582, 55.791775}, // 11

    // Pedestiran points
    {37.575589, 55.796092}, // 12
    {37.575576, 55.796162}, // 13
    {37.575559, 55.796256}, // 14
};

const geolib3::PointsVector SINGLE_SEGMENT{
    {37.612704, 55.746427},
    {37.612047, 55.747235}};

const geolib3::PointsVector TWO_SEGMENTS_STRAIGHT{
    {37.612704, 55.746427},
    {37.612318, 55.746902},
    {37.612047, 55.747235}};

const geolib3::PointsVector FOUR_SEGMENTS_QUAD{
    {37.597231, 55.756393},
    {37.596464, 55.757049},
    {37.595626, 55.756335},
    {37.596794, 55.755967}};

const geolib3::PointsVector MULTIPLE_EDGES_SEGMENT{
    {37.612976, 55.746095},
    {37.612630, 55.746523}};

void appendFeaturesInBetween(
    db::Features& result,
    db::Feature lhs,
    db::Feature rhs,
    std::size_t num,
    std::function<db::Feature(db::Feature&&)> mod =
        [](db::Feature&& feature) { return feature; })
{
    const geolib3::Segment2 segment(lhs.geodeticPos(), rhs.geodeticPos());
    const double shift = 1.0 / (num + 1);

    for (std::size_t step = 1; step <= num; ++step) {
        db::Feature feature{lhs};
        feature.setGeodeticPos(segment.pointByPosition(shift * step));
        feature.setTimestamp(feature.timestamp() + RIDE_BREAK_INTERVAL * step);
        result.push_back(mod(std::move(feature)));
    }
}

db::Features fixTestFeatureProperties(db::Features features)
{
    for (auto& feature: features) {
        feature.setSize(1920, 1080)
            .setCameraDeviation(db::CameraDeviation::Front)
            .setPrivacy(db::FeaturePrivacy::Public);
    }
    return features;
}

db::Features automotiveSpatialFeatures()
{
    /* Road graph configuration
       Notice features are marked by IDs

                two-way
                  1║▲
                  ▼║7
                   ║
                  2║▲
                  ▼║6
                   ║
               ▲  3║▲
      forbidden┘  ▼║5
    ▶─▶─▶─▶─▶─▶─▶─▶╫▶─▶ one-way
      8▶   9▶  10▶ ║
                  4║▲
                  ▼║11
                   ║
    */

    const auto EAST = geolib3::Heading{120};
    const auto NORTH = geolib3::Heading{10};
    const auto SOUTH = geolib3::Heading{190};

    return fixTestFeatureProperties({
        db::Feature{SRC_A, POINTS[0], SOUTH, TIME_0, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 1
        db::Feature{SRC_A, POINTS[1], SOUTH, TIME_1, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 2
        db::Feature{SRC_A, POINTS[2], SOUTH, TIME_2, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 3
        db::Feature{SRC_A, POINTS[3], SOUTH, TIME_3, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 4

        db::Feature{SRC_B, POINTS[2], NORTH, TIME_1, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 5
        db::Feature{SRC_B, POINTS[1], NORTH, TIME_2, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 6
        db::Feature{SRC_B, POINTS[0], NORTH, TIME_3, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 7

        db::Feature{SRC_C, POINTS[5], EAST, TIME_0, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 8
        db::Feature{SRC_C, POINTS[6], EAST, TIME_1, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 9
        db::Feature{SRC_C, POINTS[7], EAST, TIME_2, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 10

        db::Feature{SRC_C, POINTS[8], NORTH, TIME_4, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 11
    });
}

db::Features automotiveHistoricalFeatures(std::size_t number)
{
    using namespace std::literals::chrono_literals;

    constexpr auto NORTH = geolib3::Heading{10};
    const auto SOUTH = geolib3::Heading{190};

    /* Road graph configuration
       Notice features are marked by IDs

                two-way
                  1║▲
                  ▼║4
                   ║
                  2║▲
                  ▼║3
    */

    auto features = db::Features{
        db::Feature{SRC_A, POINTS[0], SOUTH, TIME_0, MDS, {}},  // 1
        db::Feature{SRC_A, POINTS[1], SOUTH, TIME_1, MDS, {}},  // 2

        db::Feature{SRC_B, POINTS[1], NORTH, TIME_0, MDS, {}},  // 3
        db::Feature{SRC_B, POINTS[0], NORTH, TIME_1, MDS, {}},  // 4
    };

    for (auto& feature : features) {
        feature.setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true);
    }

    constexpr auto year =
        std::chrono::duration_cast<std::chrono::seconds>(24h * 365);

    auto duration = year;
    const auto timestampMod = [&](db::Feature&& feature) {
        feature.setTimestamp(feature.timestamp() - duration);
        duration += RIDE_BREAK_INTERVAL;
        return feature;
    };

    duration = year;
    appendFeaturesInBetween(
        features, features.at(0), features.at(1), number / 2, timestampMod);

    duration = year;
    appendFeaturesInBetween(
        features, features.at(2), features.at(3), number - number / 2, timestampMod);

    return fixTestFeatureProperties(features);
}

db::Features pedestrianSpatialFeatures()
{
    /* Road graph configuration
       Notice features are marked by IDs

              4║▲
              ▼║3
               ║
               ║
              5║▲
              ▼║2
               ║
               ║
              6║▲
              ▼║1
    */

    const auto NORTH = geolib3::Heading{0};
    const auto SOUTH = geolib3::Heading{180};

    return fixTestFeatureProperties({
        db::Feature{SRC_A, POINTS[12], NORTH, TIME_0, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 1
        db::Feature{SRC_A, POINTS[13], NORTH, TIME_1, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 2
        db::Feature{SRC_A, POINTS[14], NORTH, TIME_2, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 3

        db::Feature{SRC_B, POINTS[14], SOUTH, TIME_1, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 4
        db::Feature{SRC_B, POINTS[13], SOUTH, TIME_2, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 5
        db::Feature{SRC_B, POINTS[12], SOUTH, TIME_3, MDS, {}}
            .setSize({6, 9})
            .setAutomaticShouldBePublished(true)
            .setIsPublished(true),  // 6
    });
}

class PhotoApiFixture : public UserMayViewPhotosFixture<true> {
public:
    PhotoApiFixture(db::Features& features)
        : UserMayViewPhotosFixture<true>([&](pgpool3::Pool& pool) {
            auto txn = pool.masterWriteableTransaction();
            for(auto& feature: features) {
                // Guarantee feature IDs persist from run to run
                db::FeatureGateway{*txn}.insert(feature);
            }
            db::updateFeaturesTransaction(features, *txn);
            txn->commit();
            Playground::instance().makeCoverage();
        })
    { }

    void update(db::Features& features) {
        auto txn = txnHandle();
        db::FeatureGateway(*txn)
            .update(features, db::UpdateFeatureTxn::No);
        txn->commit();
    }
};

template<typename SprotoType>
SprotoType specialCare(SprotoType&& obj)
{
    return obj;
}

sproto::mrcphoto::TrackPreview specialCare(
    sproto::mrcphoto::TrackPreview&& trackPreview)
{
    for (auto& chunk: trackPreview.chunk_list()->chunks()) {
        proto::mrcphoto::TrackChunkDescriptor protoDescriptor;
        Y_PROTOBUF_SUPPRESS_NODISCARD protoDescriptor.ParseFromString(
            TString(*chunk.chunk_descriptor()->payload()));
        chunk.chunk_descriptor()->payload() = protoDescriptor.DebugString();
    }

    return trackPreview;
}

template<typename SprotoType>
std::optional<SprotoType> parseForTest(const std::string& bytes)
{
    try {
        return specialCare(boost::lexical_cast<SprotoType>(bytes));
    } catch (const std::exception& e) {
        ERROR() << e.what();
        return std::nullopt;
    }
}

std::optional<std::string> performTestRequest(maps::http::MockRequest request)
{
    const auto response = yacare::performTestRequest(request);
    if (response.status != 200) {
        ERROR() << "Response status is " << response.status;
        return std::nullopt;
    }

    return response.body;
}

std::string& trimEnd(std::string& str)
{
    str.erase(std::find_if(
        str.rbegin(), str.rend(), [](unsigned char c) {
            return !std::isspace(c);
        }).base(),
        str.end());
    return str;
}

std::string getExpectedDataForTest(
    const std::string& name, const std::string& actual = "")
{
    const auto filename = SRC_(std::string("expected/") + name + ".txt");
    if (!actual.empty())
        std::ofstream(filename) << actual;

    auto data = maps::common::readFileToString(filename);
    return trimEnd(data);
}

enum class ConnectionType { UNKNOWN, UTURN, FORWARD, BACKWARD, HISTORICAL };

ConnectionType translate(sproto::mrcphoto::Photo::SpatialConnection::Type type) {
    using SpatialConnectionType =
        sproto::mrcphoto::Photo::SpatialConnection::Type;

    switch (type) {
        case SpatialConnectionType::UNKNOWN:
            return ConnectionType::UNKNOWN;
        case SpatialConnectionType::UTURN:
            return ConnectionType::UTURN;
        case SpatialConnectionType::BACKWARD:
            return ConnectionType::BACKWARD;
        case SpatialConnectionType::FORWARD:
            return ConnectionType::FORWARD;
    }
}

class ExpectedPhoto {
public:
    struct Connection {
        db::TId photoId;
        ConnectionType type;

        bool operator==(const Connection& other) const
        {
            return std::make_tuple(other.photoId, type) ==
                   std::make_tuple(photoId, type);
        }
    };

    ExpectedPhoto(db::TId photoId, std::vector<Connection> connections = {})
        : photoId_{photoId}, connections_{std::move(connections)}
    {
        sort();
    }

    ExpectedPhoto(const sproto::mrcphoto::Photo& photo)
    {
        photoId_ = boost::lexical_cast<db::TId>(photo.id());

        if (!photo.annotation()) {
            return;
        }

        for(const auto& connection: photo.annotation()->spatial_connection()) {
            connections_.push_back(
                {boost::lexical_cast<db::TId>(connection.photo_id()),
                 translate(*connection.connection_type())});
        }

        for(const auto& connection: photo.annotation()->historical_connection()) {
            connections_.push_back(
                {boost::lexical_cast<db::TId>(connection.photo_id()),
                 ConnectionType::HISTORICAL});
        }

        sort();
    }

    bool operator==(const ExpectedPhoto& other) const
    {
        return other.photoId_ == photoId_ && other.connections_ == connections_;
    }

    friend std::ostream& operator<<(
        std::ostream& out, const ExpectedPhoto& expectedPhoto);

private:
    void sort()
    {
        std::sort(
            connections_.begin(),
            connections_.end(),
            [](const auto& lhs, const auto& rhs) {
                return lhs.photoId < rhs.photoId;
            });
    }

    db::TId photoId_;
    std::vector<Connection> connections_;
};

std::ostream&
operator<<(std::ostream& out, const ExpectedPhoto::Connection& connection)
{
    switch (connection.type) {
        case ConnectionType::UTURN:
            out << "{U: ";
            break;
        case ConnectionType::BACKWARD:
            out << "{B: ";
            break;
        case ConnectionType::FORWARD:
            out << "{F: ";
            break;
        case ConnectionType::HISTORICAL:
            out << "{H: ";
            break;
        case ConnectionType::UNKNOWN:
            out << "{UNKNOWN: ";
            break;
    }
    out << connection.photoId << "}";

    return out;
}

std::ostream& operator<<(std::ostream& out, const ExpectedPhoto& expected)
{
    out  << expected.photoId_ << " -> ";
    for (const auto& connection: expected.connections_) {
        out << " " << connection;
    }
    return out;
}

template<class Type>
std::optional<std::string> loadTestData(const std::string& filename);

template<>
std::optional<std::string>
loadTestData<proto::mrcphoto::TrackChunkDescriptor>(const std::string& filename)
{
    const auto filepath = SRC_(std::string("data/") + filename);

    proto::mrcphoto::TrackChunkDescriptor message;
    TString text{maps::common::readFileToString(filepath)};
    const auto dataIsOk =
        google::protobuf::TextFormat::ParseFromString(text, &message);

    if (!dataIsOk) {
        return std::nullopt;
    }

    TString result;
    Y_PROTOBUF_SUPPRESS_NODISCARD message.SerializeToString(&result);
    return result;
}

template<>
std::optional<std::string>
loadTestData<sproto::mrcphoto::TrackPreview::ChunkDescriptor>(
    const std::string& filename)
{
    sproto::mrcphoto::TrackPreview::ChunkDescriptor result;
    const auto payload =
        loadTestData<proto::mrcphoto::TrackChunkDescriptor>(filename);

    if(!payload) {
      return std::nullopt;
    }

    result.payload() = *payload;
    return boost::lexical_cast<std::string>(result);
}

constexpr Meters MAX_FEATURES_DISTRIBUTION_DISTANCE{10};
using CoverageRing = std::vector<double>;
// Go over the track segments and cover them with photos accoring to the
// coverage percentage from the coverage ring. The items from ring are
// taken in round robin.
// The coverage item in ring can have values [-1.0, 1.0] where 0 means no
// coverage, 0.5 means the first half of a segment is covered and -0.5
// means the last half of segment is covered.
db::Features trackFeatures(
    const geolib3::Polyline2& track, const CoverageRing& coverageRing)
{
    ASSERT(!coverageRing.empty());
    auto coverageIt = coverageRing.begin();
    const auto getCoverage = [&] {
        const auto result = *coverageIt;
        coverageIt = std::next(coverageIt) == coverageRing.end()
                         ? coverageRing.begin()
                         : std::next(coverageIt);
        return result;
    };

    db::Features features;
    chrono::TimePoint timestamp =
        chrono::parseSqlDateTime("2020-01-01 12:00:00+03");
    for (const auto& segment: track.segments()) {
        const double relativeCoverage = getCoverage();
        ASSERT(-1.0 <= relativeCoverage && relativeCoverage <= 1.0);

        const Meters distanceToCover{
            geoLength(segment) * std::abs(relativeCoverage)};
        std::size_t featuresNum = std::ceil(
            distanceToCover / MAX_FEATURES_DISTRIBUTION_DISTANCE);
        const double relativeDelta = std::abs(relativeCoverage) / featuresNum;

        if(geolib3::sign(relativeCoverage) == 0) {
            continue;
        }
        double featureRelativePosition = geolib3::sign(relativeCoverage) > 0
                                             ? 0.0
                                             : 1.0 + relativeCoverage;

        const auto heading =
            geolib3::Direction2(geolib3::convertGeodeticToMercator(segment))
                .heading();

        while (featuresNum--) {
            features
                .emplace_back(SRC_A,
                              segment.pointByPosition(featureRelativePosition),
                              heading,
                              chrono::formatSqlDateTime(timestamp),
                              MDS,
                              db::Dataset::Agents)
                .setSize({6, 9})
                .setAutomaticShouldBePublished(true)
                .setIsPublished(true);

            featureRelativePosition += relativeDelta;
            timestamp += RIDE_BREAK_INTERVAL;
        }
    }
    return fixTestFeatureProperties(std::move(features));
}

std::string makeTrackPreviewRequest(const geolib3::Polyline2& track)
{
    sproto::mrcphoto::PreviewRequest previewRequest;
    previewRequest.polyline() = geolib3::sproto::encode(track);
    return boost::lexical_cast<std::string>(previewRequest);
}

} // namespace

#define EXPECT_PHOTO(expected, DATA)                                      \
    {                                                                     \
        const auto actual = parseForTest<sproto::mrcphoto::Photo>(*DATA); \
        ASSERT_TRUE(actual);                                              \
        EXPECT_EQ(ExpectedPhoto{*actual}, expected);                      \
    }

#define EXPECT_TEST_OUTPUT_DUMP(DATA, SPROTOTYPE, DUMP)        \
    {                                                          \
        ASSERT_TRUE(DATA);                                     \
        const auto obj = parseForTest<SPROTOTYPE>(*DATA);      \
        const std::string actual =                             \
            boost::lexical_cast<std::string>(textDump(*obj));  \
        const auto expected =                                  \
            getExpectedDataForTest(Name_, DUMP ? actual : ""); \
        EXPECT_EQ(actual, expected);                           \
    }

// Toggle 'false' to 'true' to regenerate tests data
#define EXPECT_TRACK_PREVIEW(DATA) \
    EXPECT_TEST_OUTPUT_DUMP(DATA, sproto::mrcphoto::TrackPreview, false)

#define EXPECT_TRACK_CHUNK(DATA) \
    EXPECT_TEST_OUTPUT_DUMP(DATA, sproto::mrcphoto::TrackChunk, false)

#define AUTOMOTIVE_SPATIAL_FIXTURE               \
    auto features = automotiveSpatialFeatures(); \
    PhotoApiFixture fixture(features);

#define AUTOMOTIVE_HISTORY_FIXTURE(N)                \
    auto features = automotiveHistoricalFeatures(N); \
    PhotoApiFixture fixture(features);

#define PEDESTRIAN_SPATIAL_FIXTURE               \
    auto features = pedestrianSpatialFeatures(); \
    PhotoApiFixture fixture(features);

#define PEDESTRIAN_HISTORY_FIXTURE(N)                \
    auto features = pedestrianHistoricalFeatures(N); \
    PhotoApiFixture fixture(features);

#define EMPTY_FIXTURE      \
    db::Features features; \
    PhotoApiFixture fixture(features);

#define TRACK_FIXTURE(TRACK, COVERAGE_RING)                    \
    db::Features features = trackFeatures(TRACK, COVERAGE_RING); \
    PhotoApiFixture fixture(features);

Y_UNIT_TEST_SUITE(photo_api_photo_should)
{
    Y_UNIT_TEST(photo_api_mrcauto_hotspot_three_in_line)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;

        const auto binaryURL =
            http::URL("http://localhost/v2/photo/hotspot")
                .addParam("l", "mrcauto")
                .addParam("ll", "37.569406,55.791476") // point-feature: 6-9
                .addParam("z", "17");

        // Get binary representation
        {
            http::MockRequest request(http::GET, binaryURL);
            const auto photo = performTestRequest(request);

            const ExpectedPhoto expected{
                9, {{8, ConnectionType::BACKWARD},
                    {10, ConnectionType::FORWARD}}};

            EXPECT_PHOTO(expected, photo);
        }

        // Get text representation
        {
            http::URL textURL(binaryURL);
            textURL.addParam("format", "text");

            http::MockRequest request(http::GET, textURL);

            const auto response = yacare::performTestRequest(request);
            ASSERT_EQ(response.status, 200);

            const auto expected = getExpectedDataForTest(
                "photo_api_mrcauto_hotspot_three_in_line");
            EXPECT_EQ(response.body, expected);
        }
    }

    Y_UNIT_TEST(photo_api_mrcauto_hotspot_point_on_lhs_of_road)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/hotspot")
                .addParam("l", "mrcauto")
                .addParam(
                    "ll", "37.569553,55.791514") // closest point-feature: 1-2
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            2, {{6, ConnectionType::UTURN},
                {1, ConnectionType::BACKWARD},
                {3, ConnectionType::FORWARD}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcauto_hotspot_point_on_rhs_of_road)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/hotspot")
                .addParam("l", "mrcauto")
                .addParam(
                    "ll", "37.569730,55.791507") // closest point-feature: 1-6
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            6, {{2, ConnectionType::UTURN},
                {5, ConnectionType::BACKWARD},
                {7, ConnectionType::FORWARD}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcauto_hotspot_point_in_between)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/hotspot")
                .addParam("l", "mrcauto")
                .addParam(
                    "ll",
                    "37.569491,55.791393") // closest point-feature: 7-9
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            10, {{9, ConnectionType::BACKWARD},
                 {4, ConnectionType::FORWARD},
                 {5, ConnectionType::FORWARD}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcauto_hotspot_historical_photos_clustering)
    {
        AUTOMOTIVE_HISTORY_FIXTURE(8);

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/hotspot")
                .addParam("l", "mrcauto")
                .addParam(
                    "ll",
                    "37.569570,55.791620") // closest point-feature: 0-1
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            1, {{2, ConnectionType::FORWARD},
                {4, ConnectionType::UTURN},
                {5, ConnectionType::HISTORICAL},
                {6, ConnectionType::HISTORICAL}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcauto_hotspot_historical_photos_reduction)
    {
        AUTOMOTIVE_HISTORY_FIXTURE(400);

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/hotspot")
                .addParam("l", "mrcauto")
                .addParam(
                    "ll",
                    "37.569657,55.791510") // closest point-feature: 1-3
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            3, {{2, ConnectionType::UTURN},
                {4, ConnectionType::FORWARD},
                {205, ConnectionType::HISTORICAL},
                {215, ConnectionType::HISTORICAL},
                {225, ConnectionType::HISTORICAL},
                {235, ConnectionType::HISTORICAL},
                {245, ConnectionType::HISTORICAL},
                {255, ConnectionType::HISTORICAL},
                {265, ConnectionType::HISTORICAL},
                {275, ConnectionType::HISTORICAL},
                {285, ConnectionType::HISTORICAL},
                {295, ConnectionType::HISTORICAL}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcauto_hotspot_unpublished_photo)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;

        features.at(1).setAutomaticShouldBePublished(false);
        fixture.update(features);

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/hotspot")
                .addParam("l", "mrcauto")
                .addParam(
                    "ll", "37.569553,55.791514") // closest point-feature: 1-2
                .addParam("z", "17"));

        const auto photo =
            performTestRequest(request);

        const ExpectedPhoto expected{
            3, {{5, ConnectionType::UTURN},
                {1, ConnectionType::BACKWARD},
                {4, ConnectionType::FORWARD}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcauto_get_three_in_line)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/get")
                .addParam("l", "mrcauto")
                .addParam("id", "9")
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            9, {{8, ConnectionType::BACKWARD},
                {10, ConnectionType::FORWARD}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcauto_get_by_historical_photo)
    {
        AUTOMOTIVE_HISTORY_FIXTURE(8);

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/get")
                .addParam("l", "mrcauto")
                .addParam("id", "5")
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            5, {{4, ConnectionType::UTURN},
                {2, ConnectionType::FORWARD},
                {1, ConnectionType::HISTORICAL},
                {6, ConnectionType::HISTORICAL}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcauto_get_skip_unpublished_behind)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;

        features.at(4).setAutomaticShouldBePublished(false);
        fixture.update(features);

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/get")
                .addParam("l", "mrcauto")
                .addParam("id", "6")
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            6, {{2, ConnectionType::UTURN},
                {10, ConnectionType::BACKWARD},
                {11, ConnectionType::BACKWARD},
                {7, ConnectionType::FORWARD}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcauto_get_skip_unpublished_ahead)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;

        features[9].setAutomaticShouldBePublished(false);
        fixture.update(features);

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/get")
                .addParam("l", "mrcauto")
                .addParam("id", "9")
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            9, {{8, ConnectionType::UTURN},
                {4, ConnectionType::BACKWARD},
                {5, ConnectionType::FORWARD}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcauto_get_unpublished_uturn)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;

        features[5].setAutomaticShouldBePublished(false);
        fixture.update(features);

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/get")
                .addParam("l", "mrcauto")
                .addParam("id", "2")
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            2, {{5, ConnectionType::UTURN},
                {1, ConnectionType::BACKWARD},
                {3, ConnectionType::FORWARD}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcpdst_hotspot_point_on_lhs_of_road)
    {
        PEDESTRIAN_SPATIAL_FIXTURE;

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/hotspot")
                .addParam("l", "mrcpdst")
                .addParam(
                    "ll",
                    "37.575516,55.796162") // closest point-feature: 13-5
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            5, {{2, ConnectionType::UTURN},
                {4, ConnectionType::BACKWARD},
                {6, ConnectionType::FORWARD}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_mrcpdst_get_point_on_lhs_of_road)
    {
        PEDESTRIAN_SPATIAL_FIXTURE;

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/get")
                .addParam("l", "mrcpdst")
                .addParam("id", "5")
                .addParam("z", "17"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{
            5, {{2, ConnectionType::UTURN},
                {4, ConnectionType::BACKWARD},
                {6, ConnectionType::FORWARD}}};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_strip)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/strip").
                addParam("id", "9"));

        const auto photo = performTestRequest(request);

        const ExpectedPhoto expected{9};

        EXPECT_PHOTO(expected, photo);
    }

    Y_UNIT_TEST(photo_api_photos_get)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;
        ASSERT(!features.empty());
        auto ids = wiki::common::join(
            features,
            [](const auto& feature) { return std::to_string(feature.id()); },
            ",");
        auto req = http::MockRequest(http::GET,
                                     http::URL("http://localhost/v2/photos/get")
                                         .addParam("l", "mrcauto")
                                         .addParam("ids", ids)
                                         .addParam("z", "17"));
        auto resp = performTestRequest(req);
        auto photoList = parseForTest<sproto::mrcphoto::PhotoList>(*resp);
        ASSERT(photoList);
        EXPECT_EQ(photoList->photos().size(), features.size());
        for (size_t i = 0; i < features.size(); ++i) {
            req = http::MockRequest(
                http::GET,
                http::URL("http://localhost/v2/photo/get")
                    .addParam("l", "mrcauto")
                    .addParam("id", std::to_string(features[i].id()))
                    .addParam("z", "17"));
            resp = performTestRequest(req);
            auto photo = parseForTest<sproto::mrcphoto::Photo>(*resp);
            ASSERT(photo);
            EXPECT_EQ(ExpectedPhoto{*photo},
                      ExpectedPhoto{photoList->photos().at(i)});
        }
    }

} // Y_UNIT_TEST_SUITE(photo_api_photo_should)

Y_UNIT_TEST_SUITE(photo_api_photo_negative_tests)
{
    Y_UNIT_TEST(photo_api_empty_layer)
    {
        EMPTY_FIXTURE;

        for (const auto& request : {
                 http::MockRequest(http::GET,
                                   http::URL("http://localhost/v2/photo/get")
                                       .addParam("l", "")
                                       .addParam("id", "1")
                                       .addParam("z", "17")),
                 http::MockRequest(http::GET,
                                   http::URL("http://localhost/v2/photos/get")
                                       .addParam("l", "")
                                       .addParam("ids", "1")
                                       .addParam("z", "17")),
             }) {
            const auto response = yacare::performTestRequest(request);
            EXPECT_EQ(response.status, 400);
        }
    }

    Y_UNIT_TEST(photo_api_unexpected_layer)
    {
        EMPTY_FIXTURE;

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/hotspot")
                .addParam("l", "unexpected")
                .addParam("ll", "37.569657,55.791510")
                .addParam("z", "17"));
        const auto response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 400);
    }

    Y_UNIT_TEST(photo_api_no_covered_ege_around)
    {
        EMPTY_FIXTURE;

        http::MockRequest request(
            http::GET,
            http::URL("http://localhost/v2/photo/hotspot")
                .addParam("l", "mrcauto")
                .addParam("ll", "37.569657,55.791510")
                .addParam("z", "17"));
        const auto response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 204);
    }

    Y_UNIT_TEST(photo_api_no_such_photo_id)
    {
        EMPTY_FIXTURE;

        for (const auto& request : {
                 http::MockRequest(http::GET,
                                   http::URL("http://localhost/v2/photo/get")
                                       .addParam("l", "mrcauto")
                                       .addParam("id", "1")
                                       .addParam("z", "17")),
                 http::MockRequest(http::GET,
                                   http::URL("http://localhost/v2/photos/get")
                                       .addParam("l", "mrcauto")
                                       .addParam("ids", "1")
                                       .addParam("z", "17")),
             }) {
            const auto response = yacare::performTestRequest(request);
            EXPECT_EQ(response.status, 404);
        }
    }

    Y_UNIT_TEST(photo_api_mrcauto_get_unpublished_photo)
    {
        AUTOMOTIVE_SPATIAL_FIXTURE;

        features[1].setAutomaticShouldBePublished(false);
        fixture.update(features);

        for (const auto& request : {
                 http::MockRequest(http::GET,
                                   http::URL("http://localhost/v2/photo/get")
                                       .addParam("l", "mrcauto")
                                       .addParam("id", "2")
                                       .addParam("z", "17")),
                 http::MockRequest(http::GET,
                                   http::URL("http://localhost/v2/photos/get")
                                       .addParam("l", "mrcauto")
                                       .addParam("ids", "2")
                                       .addParam("z", "17")),
             }) {
            const auto response = yacare::performTestRequest(request);
            EXPECT_EQ(response.status, 422);
        }
    }
} // Y_UNIT_TEST_SUITE(photo_api_photo_negative_tests)

Y_UNIT_TEST_SUITE(photo_api_track_tests)
{
    Y_UNIT_TEST(photo_api_track_preview_straight_single_chunk) {

        const geolib3::Polyline2 SINGLE_SEGMENT_TRACK{SINGLE_SEGMENT};

        const CoverageRing COVERAGE_RING = {1.0};

        TRACK_FIXTURE(SINGLE_SEGMENT_TRACK, COVERAGE_RING);

        http::MockRequest request = {
            http::POST,
            http::URL("http://localhost/v2/track/preview")
                .addParam("limit", "10000")};

        request.body = makeTrackPreviewRequest(SINGLE_SEGMENT_TRACK);

        const auto trackPreview = performTestRequest(request);

        EXPECT_TRACK_PREVIEW(trackPreview);
    }

    Y_UNIT_TEST(photo_api_track_preview_straight_two_chunks) {
        const geolib3::Polyline2 TWO_SEGMENTS_TRACK{TWO_SEGMENTS_STRAIGHT};

        const CoverageRing COVERAGE_RING = {0.77, -0.77};

        TRACK_FIXTURE(TWO_SEGMENTS_TRACK, COVERAGE_RING);

        http::MockRequest request = {
            http::POST,
            http::URL("http://localhost/v2/track/preview")
                .addParam("limit", "5")};

        request.body = makeTrackPreviewRequest(TWO_SEGMENTS_TRACK);

        const auto trackPreview = performTestRequest(request);

        EXPECT_TRACK_PREVIEW(trackPreview);
    }

    Y_UNIT_TEST(photo_api_track_preview_straight_multiple_tiny_chunks) {

        const geolib3::Polyline2 SINGLE_SEGMENT_TRACK{SINGLE_SEGMENT};

        const CoverageRing COVERAGE_RING = {1.0};

        TRACK_FIXTURE(SINGLE_SEGMENT_TRACK, COVERAGE_RING);

        http::MockRequest request = {
            http::POST,
            http::URL("http://localhost/v2/track/preview")
                .addParam("limit", "1")};

        request.body = makeTrackPreviewRequest(SINGLE_SEGMENT_TRACK);

        const auto trackPreview = performTestRequest(request);

        EXPECT_TRACK_PREVIEW(trackPreview);
    }

    Y_UNIT_TEST(photo_api_track_preview_quad_single_chunk)
    {
        const geolib3::Polyline2 QUAD_TRACK{geolib3::PointsVector{
            FOUR_SEGMENTS_QUAD[0],
            FOUR_SEGMENTS_QUAD[1],
            FOUR_SEGMENTS_QUAD[2],
            FOUR_SEGMENTS_QUAD[3],
            FOUR_SEGMENTS_QUAD[0]}};

        const CoverageRing COVERAGE_RING = {1.0};

        TRACK_FIXTURE(QUAD_TRACK, COVERAGE_RING);

        http::MockRequest request = {
            http::POST,
            http::URL("http://localhost/v2/track/preview")
                .addParam("limit", "10000")};

        request.body = makeTrackPreviewRequest(QUAD_TRACK);

        const auto trackPreview = performTestRequest(request);

        EXPECT_TRACK_PREVIEW(trackPreview);
    }

    Y_UNIT_TEST(photo_api_track_preview_quad_two_chunks)
    {
        const geolib3::Polyline2 QUAD_TRACK{geolib3::PointsVector{
            FOUR_SEGMENTS_QUAD[0],
            FOUR_SEGMENTS_QUAD[1],
            FOUR_SEGMENTS_QUAD[2],
            FOUR_SEGMENTS_QUAD[3],
            FOUR_SEGMENTS_QUAD[0]}};

        const CoverageRing COVERAGE_RING = {1.0, 0.5, -0.5, 1.0};

        TRACK_FIXTURE(QUAD_TRACK, COVERAGE_RING);

        http::MockRequest request = {
            http::POST,
            http::URL("http://localhost/v2/track/preview")
                .addParam("limit", "14")};

        request.body = makeTrackPreviewRequest(QUAD_TRACK);

        const auto trackPreview = performTestRequest(request);

        EXPECT_TRACK_PREVIEW(trackPreview);
    }

    Y_UNIT_TEST(photo_api_track_preview_quad_self_intersecting_one_chunk)
    {

        const geolib3::Polyline2 SELF_INTERSECTING_TRACK{
            geolib3::PointsVector{
                FOUR_SEGMENTS_QUAD[0],
                FOUR_SEGMENTS_QUAD[1],
                FOUR_SEGMENTS_QUAD[2],
                FOUR_SEGMENTS_QUAD[3],
                FOUR_SEGMENTS_QUAD[0],
                FOUR_SEGMENTS_QUAD[1]}};

        const CoverageRing COVERAGE_RING = {1.0, 1.0, 1.0, 1.0, 0.0, 0.0};

        TRACK_FIXTURE(SELF_INTERSECTING_TRACK, COVERAGE_RING);

        http::MockRequest request = {
            http::POST,
            http::URL("http://localhost/v2/track/preview")
                .addParam("limit", "10000")};

        request.body = makeTrackPreviewRequest(SELF_INTERSECTING_TRACK);

        const auto trackPreview = performTestRequest(request);

        EXPECT_TRACK_PREVIEW(trackPreview);
    }

    Y_UNIT_TEST(photo_api_track_chunk_straight_tiny_chunk) {
        const geolib3::Polyline2 SINGLE_SEGMENT_TRACK{SINGLE_SEGMENT};

        const CoverageRing COVERAGE_RING = {1.0};

        TRACK_FIXTURE(SINGLE_SEGMENT_TRACK, COVERAGE_RING);

        const auto chunkDescriptor =
          loadTestData<sproto::mrcphoto::TrackPreview::ChunkDescriptor>(
                "tiny_chunk_payload_for_single_segment_track.txt");
        ASSERT_TRUE(chunkDescriptor);

        http::MockRequest request = {
            http::POST, http::URL("http://localhost/v2/track/chunk")};
        request.body = *chunkDescriptor;

        const auto chunkData = performTestRequest(request);

        EXPECT_TRACK_CHUNK(chunkData);
    }

    Y_UNIT_TEST(photo_api_track_chunk_straight_single_chunk)
    {
        const geolib3::Polyline2 SINGLE_SEGMENT_TRACK{SINGLE_SEGMENT};

        const CoverageRing COVERAGE_RING = {1.0};

        TRACK_FIXTURE(SINGLE_SEGMENT_TRACK, COVERAGE_RING);

        const auto chunkDescriptor =
          loadTestData<sproto::mrcphoto::TrackPreview::ChunkDescriptor>(
                "chunk_payload_for_single_segment_track.txt");
        ASSERT_TRUE(chunkDescriptor);

        http::MockRequest request = {
            http::POST, http::URL("http://localhost/v2/track/chunk")};
        request.body = *chunkDescriptor;

        const auto chunkData = performTestRequest(request);

        EXPECT_TRACK_CHUNK(chunkData);
    }

    Y_UNIT_TEST(photo_api_track_chunk_straight_second_chunk) {
        const geolib3::Polyline2 TWO_SEGMENTS_TRACK{TWO_SEGMENTS_STRAIGHT};

        const CoverageRing COVERAGE_RING = {0.77, -0.77};

        TRACK_FIXTURE(TWO_SEGMENTS_TRACK, COVERAGE_RING);

        const auto chunkDescriptor =
          loadTestData<sproto::mrcphoto::TrackPreview::ChunkDescriptor>(
                "second_chunk_payload_for_two_segments_track.txt");
        ASSERT_TRUE(chunkDescriptor);

        http::MockRequest request = {
            http::POST, http::URL("http://localhost/v2/track/chunk")};
        request.body = *chunkDescriptor;

        const auto chunkData = performTestRequest(request);

        EXPECT_TRACK_CHUNK(chunkData);
    }

    Y_UNIT_TEST(photo_api_track_chunk_large_quad_chunk) {
        const geolib3::Polyline2 QUAD_TRACK{geolib3::PointsVector{
            FOUR_SEGMENTS_QUAD[0],
            FOUR_SEGMENTS_QUAD[1],
            FOUR_SEGMENTS_QUAD[2],
            FOUR_SEGMENTS_QUAD[3],
            FOUR_SEGMENTS_QUAD[0]}};

        const CoverageRing COVERAGE_RING = {1.0};

        TRACK_FIXTURE(QUAD_TRACK, COVERAGE_RING);

        const auto chunkDescriptor =
          loadTestData<sproto::mrcphoto::TrackPreview::ChunkDescriptor>(
                "large_chunk_payload_for_self_intersecting_track.txt");
        ASSERT_TRUE(chunkDescriptor);

        http::MockRequest request = {
            http::POST, http::URL("http://localhost/v2/track/chunk")};
        request.body = *chunkDescriptor;

        const auto chunkData = performTestRequest(request);

        EXPECT_TRACK_CHUNK(chunkData);
    }
} // Y_UNIT_TEST_SUITE(photo_api_track_tests)

Y_UNIT_TEST_SUITE(photo_api_track_negative_tests)
{
    Y_UNIT_TEST(photo_api_track_preview_no_content)
    {
        EMPTY_FIXTURE;

        const geolib3::Polyline2 SINGLE_SEGMENT_TRACK{SINGLE_SEGMENT};

        http::MockRequest request = {
            http::POST,
            http::URL("http://localhost/v2/track/preview")
                .addParam("limit", "10000")};

        request.body = makeTrackPreviewRequest(SINGLE_SEGMENT_TRACK);

        const auto response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 204);
    }

    Y_UNIT_TEST(photo_api_track_zero_length_track_preview)
    {
        const geolib3::Polyline2 SINGLE_SEGMENT_TRACK{SINGLE_SEGMENT};

        const CoverageRing COVERAGE_RING = {1.0};

        TRACK_FIXTURE(SINGLE_SEGMENT_TRACK, COVERAGE_RING);

        http::MockRequest request = {
            http::POST,
            http::URL("http://localhost/v2/track/preview")
                .addParam("limit", "10000")};

        const geolib3::Polyline2 ZERO_LENGTH_TRACK{geolib3::PointsVector{
            SINGLE_SEGMENT[0],
            SINGLE_SEGMENT[0]}};

        request.body = makeTrackPreviewRequest(ZERO_LENGTH_TRACK);

        const auto response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 204);
    }

    Y_UNIT_TEST(photo_api_track_corrupted_preview_request)
    {
        EMPTY_FIXTURE;

        http::MockRequest request = {
            http::POST,
            http::URL("http://localhost/v2/track/preview")
                .addParam("limit", "10000")};

        request.body = "CORRUPTED TRACK";

        const auto response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 400);
    }

    Y_UNIT_TEST(photo_api_track_chunk_no_content)
    {
        EMPTY_FIXTURE;

        const auto chunkDescriptor =
          loadTestData<sproto::mrcphoto::TrackPreview::ChunkDescriptor>(
                "chunk_payload_for_single_segment_track.txt");
        ASSERT_TRUE(chunkDescriptor);

        http::MockRequest request = {
            http::POST, http::URL("http://localhost/v2/track/chunk")};
        request.body = *chunkDescriptor;

        const auto response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 204);
    }

    Y_UNIT_TEST(photo_api_track_chunk_corrupted_request)
    {
        EMPTY_FIXTURE;

        http::MockRequest request = {
            http::POST, http::URL("http://localhost/v2/track/chunk")};
        request.body = "CORRUPTED CHUNK";

        const auto response = yacare::performTestRequest(request);
        EXPECT_EQ(response.status, 400);
    }
} // Y_UNIT_TEST_SUITE(photo_api_track_negative_tests)

Y_UNIT_TEST_SUITE(photo_api_track_regression_tests)
{
    Y_UNIT_TEST(photo_api_track_preview_coalescing)
    {
        const geolib3::Polyline2 MULTIPLE_EDGES_TRACK{MULTIPLE_EDGES_SEGMENT};

        const CoverageRing COVERAGE_RING = {1.0};

        TRACK_FIXTURE(MULTIPLE_EDGES_TRACK, COVERAGE_RING);

        http::MockRequest request = {
            http::POST,
            http::URL("http://localhost/v2/track/preview")
                .addParam("limit", "10000")};

        request.body = makeTrackPreviewRequest(MULTIPLE_EDGES_TRACK);

        const auto trackPreview = performTestRequest(request);

        EXPECT_TRACK_PREVIEW(trackPreview);
    }

    Y_UNIT_TEST(photo_api_track_chunk_coalescing)
    {
        const geolib3::Polyline2 MULTIPLE_EDGES_TRACK{MULTIPLE_EDGES_SEGMENT};

        const CoverageRing COVERAGE_RING = {1.0};

        TRACK_FIXTURE(MULTIPLE_EDGES_TRACK, COVERAGE_RING);

        const auto chunkDescriptor =
          loadTestData<sproto::mrcphoto::TrackPreview::ChunkDescriptor>(
                "chunk_payload_for_multiple_edges_track.txt");
        ASSERT_TRUE(chunkDescriptor);

        http::MockRequest request = {
            http::POST, http::URL("http://localhost/v2/track/chunk")};
        request.body = *chunkDescriptor;

        const auto chunkData = performTestRequest(request);

        EXPECT_TRACK_CHUNK(chunkData);
    }
} // Y_UNIT_TEST_SUITE(photo_api_track_regression_tests)

} // namespace maps::mrc::browser::tests
