#include <maps/wikimap/mapspro/services/mrc/libs/video_frames_reader/include/frames_reader.h>

#include <maps/libs/common/include/file_utils.h>
#include <maps/libs/log8/include/log8.h>

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

using namespace testing;

namespace maps::mrc::video {
namespace tests {

namespace {

static const std::string DATA_DIR =
    "maps/wikimap/mapspro/services/mrc/libs/video_frames_reader/tests/data/";

} // namespace

TEST(video_frames_reader_should, read_frame_from_video)
{
    auto videoPath = static_cast<std::string>(BinaryPath(DATA_DIR + "video.mp4"));
    auto framePath = static_cast<std::string>(BinaryPath(DATA_DIR + "frame42.jpg"));

    auto frame42 = maps::common::readFileToString(framePath);

    maps::mrc::video::VideoFramesReader videoFramesReader;

    size_t framesCount = 0;
    videoFramesReader.open(videoPath);

    EXPECT_EQ(videoFramesReader.duration().count(), 2165);

    while (true) {
        auto frame = videoFramesReader.readFrame();
        if (!frame) {
            break;
        }

        ++framesCount;
        if (framesCount == 42) {
            std::vector<uchar> buf;
            cv::imencode(".jpg", frame->frame, buf);
            std::string encoded((const char*)(buf.data()), buf.size());
            EXPECT_EQ(encoded, frame42);
            EXPECT_EQ(frame->timeFromStart.count(), 1365);
        }
    }

    EXPECT_EQ(framesCount, 65u);
    videoFramesReader.close();
}

TEST(video_frames_reader_should, seek_to_video_position)
{
    auto videoPath = static_cast<std::string>(BinaryPath(DATA_DIR + "video.mp4"));

    maps::mrc::video::VideoFramesReader videoFramesReader;

    videoFramesReader.open(videoPath);

    // Read some frames
    for (int i = 0; i < 3; ++i) {
        auto frame = videoFramesReader.readFrame();
        EXPECT_TRUE(frame.has_value());
    }

    // Seek forward
    videoFramesReader.seek(std::chrono::seconds(2));
    auto frame = videoFramesReader.readFrame();
    EXPECT_TRUE(frame.has_value());
    EXPECT_EQ(frame->timeFromStart.count(), 1998);

    // Read some more frames
    for (int i = 0; i < 3; ++i) {
        frame = videoFramesReader.readFrame();
        EXPECT_TRUE(frame.has_value());
    }

    // Seek backward
    videoFramesReader.seek(std::chrono::seconds(1));
    frame = videoFramesReader.readFrame();
    EXPECT_EQ(frame->timeFromStart.count(), 999);

    // Seek beyound video duration
    EXPECT_THROW(
        videoFramesReader.seek(std::chrono::seconds(3)),
        maps::RuntimeError);

    videoFramesReader.close();
}

TEST(video_frames_reader_should, read_video_twice)
{
    auto videoPath = static_cast<std::string>(BinaryPath(DATA_DIR + "video.mp4"));
    constexpr double FPS = 30.0205;

    maps::mrc::video::VideoFramesReader videoFramesReader;

    // Read video file twice with the same VideoFramesReader instance
    for (int i = 0; i < 2; ++i) {
        size_t framesCount = 0;
        videoFramesReader.open(videoPath);

        while (true) {
            auto frame = videoFramesReader.readFrame();
            if (!frame) {
                break;
            }
            EXPECT_EQ(
                frame->timeFromStart.count(),
                static_cast<int>(framesCount / FPS * 1000));

            ++framesCount;
        }
        EXPECT_EQ(framesCount, 65u);
        videoFramesReader.close();
    }
}

TEST(video_frames_reader_should, read_invalid_video_file)
{
    auto invalidVideoPath = static_cast<std::string>(BinaryPath(DATA_DIR)) + "invalid.mp4";
    maps::common::writeFile(invalidVideoPath, "invalid content");

    maps::mrc::video::VideoFramesReader videoFramesReader;

    EXPECT_THROW(videoFramesReader.open(invalidVideoPath), maps::RuntimeError);

    // Check that a valid file can be read after exception
    size_t framesCount = 0;
    videoFramesReader.open(BinaryPath(DATA_DIR + "video.mp4"));

    while (true) {
        auto frame = videoFramesReader.readFrame();
        if (!frame) {
            break;
        }
        ++framesCount;
    }
    EXPECT_EQ(framesCount, 65u);
    videoFramesReader.close();
}


} // namespace tests
} // namespace maps::mrc::video
