#pragma once

#include <maps/wikimap/mapspro/services/mrc/eye/lib/import_panorama_frame/include/import.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/panorama_frame.h>

#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye_panorama.h>
#include <maps/wikimap/mapspro/services/mrc/libs/unittest/include/yandex/maps/mrc/unittest/database_fixture.h>

#include <library/cpp/iterator/zip.h>

#include <library/cpp/testing/gtest/gtest.h>

#include <map>
#include <vector>

namespace maps::mrc::eye::tests {

struct ImportPanoramaFrameFixture: testing::Test {
    struct State {
        // Eye panoramas are lexicographically ordered by sessionId and orderNum.
        db::EyePanoramas panoramas;

        // Panorama session to devices relation is ordered by session ID.
        db::eye::PanoramaSessionToDevices panoramaSessionToDevices;
        // Devices are ordered accordingly to panoramaSessionToDevices.
        db::eye::Devices devices;

        // panoramaToFrames are ordered by panorama sessionId, orderNum, and frame
        // deviation.
        db::eye::PanoramaToFrames panoramaToFrames;
        // Frames are ordered accordingly with panoramaToFrames.
        db::eye::Frames frames;
        // Locations are ordered accordingly to frames.
        db::eye::FrameLocations locations;
        // frames, panoramaToFrames, frames, and locations obey to SoA idiom
        // so could be zipped together.

        std::map<db::TId, const db::EyePanorama*> frameIdToPanorama;
        std::map<db::TId, const db::eye::Device*> frameIdToDevice;
        std::map<db::TId, const db::eye::PanoramaSessionToDevice*>
            frameIdToPanoramaSessionToDevice;

        struct {
            db::TId frameTxnId{0};
            std::uint64_t sessionId{0};
            std::uint32_t orderNum{0};
        } metadata;

        bool consistent{false};
    };

    ImportPanoramaFrameFixture();

    void makeEyePanoramas(std::uint64_t sessionId, std::size_t num);

    void setDeleted(const db::PanoramaOIDs& oids, bool deleted);

    ImportPanoramaFrame importPanoramaFrame() const;

    const State& getState();

private:
    bool isDirty() const;

    void reloadState();

    bool dirty_{true};
    State state_;
};

#define TEST_CONSTANTS(PANORAMAS_NUM)                        \
    constexpr std::size_t EYE_PANORAMAS_NUM = PANORAMAS_NUM; \
    constexpr std::size_t PANORAMA_FRAMES_NUM =              \
        PANORAMA_FRAME_DEVIATIONS_SIZE * EYE_PANORAMAS_NUM;

#define ASSERT_STATE_CONSISTENCY(STATE, PANORAMAS_NUM, FRAMES_NUM)           \
    ASSERT_EQ(STATE.panoramas.size(), PANORAMAS_NUM);                        \
    ASSERT_EQ(STATE.frames.size(), FRAMES_NUM);                              \
    {                                                                        \
        for (const auto& [panoramaToFrame, frame, location]:                 \
             Zip(STATE.panoramaToFrames, STATE.frames, STATE.locations)) {   \
            const db::TId frameId = frame.id();                              \
            ASSERT_EQ(frameId, panoramaToFrame.frameId());                   \
            ASSERT_EQ(frameId, location.frameId());                          \
            ASSERT_TRUE(STATE.frameIdToPanorama.at(frameId));                \
            ASSERT_TRUE(STATE.frameIdToPanoramaSessionToDevice.at(frameId)); \
            ASSERT_TRUE(STATE.frameIdToDevice.at(frameId));                  \
        }                                                                    \
        STATE.consistent = true;                                             \
    }

#define ASSERT_FINAL_STATE_CONSISTENCY(STATE) \
    ASSERT_STATE_CONSISTENCY(STATE, EYE_PANORAMAS_NUM, PANORAMA_FRAMES_NUM)

inline auto zipped(const ImportPanoramaFrameFixture::State& state)
{
    ASSERT(state.consistent);
    return Zip(state.panoramaToFrames, state.frames, state.locations);
}

inline auto relations(const ImportPanoramaFrameFixture::State& state,
                      db::TId frameId)
{
    ASSERT(state.consistent);
    return std::make_tuple(
        state.frameIdToPanorama.at(frameId),
        state.frameIdToPanoramaSessionToDevice.at(frameId),
        state.frameIdToDevice.at(frameId));
}

} // namespace maps::mrc::eye::tests
