#pragma once

#include "../lib/message_reporter.h"
#include "../lib/task_params.h"

#include <yandex/maps/wiki/revision/revisionsgateway.h>
#include <yandex/maps/wiki/unittest/arcadia.h>
#include <maps/libs/log8/include/log8.h>

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

#include <filesystem>

namespace maps {
namespace wiki {
namespace importer {

constexpr uint64_t TEST_TASK_ID = 1;
constexpr uint64_t TEST_UID = 1;

const TString SERVICES_CONFIG_PATH =
    ArcadiaSourceRoot() + "/maps/wikimap/mapspro/cfg/services/services.development.xml";
const TString EDITOR_CONFIG_PATH =
    ArcadiaSourceRoot() + "/maps/wikimap/mapspro/cfg/editor/editor.xml";

std::filesystem::path dataPath(const std::string& relativeFilepath);

enum class ApproveCommits
{
    Yes,
    No
};

enum class CheckCommits
{
    Yes,
    No
};

struct SetLogLevelFixture
{
    SetLogLevelFixture() { maps::log8::setLevel(maps::log8::Level::ERROR); }
};

class RandomDbFixture
    : public SetLogLevelFixture
    , public unittest::ArcadiaDbFixture
{
public:
    RandomDbFixture()
        : SetLogLevelFixture()
        , ArcadiaDbFixture()
    {
    }

    void performAction(
        Action action,
        const std::filesystem::path& dataDir,
        size_t expectedWarningCount = 0,
        size_t skippedObjectCount = 0,
        CheckCommits checkCommits = CheckCommits::Yes,
        ApproveCommits approveCommits = ApproveCommits::Yes);

private:
    void performActionImpl(
        Action action,
        const std::filesystem::path& dataDir,
        size_t expectedWarningCount,
        size_t skippedObjectCount,
        CheckCommits checkCommits,
        ApproveCommits approveCommits);
};

void checkMessageReporter(const MessageReporter& messageReporter, size_t expectedWarningCount = 0);

struct TestRelation;

struct TestObject
{
    StringMap attributes;
    std::vector<TestRelation> slaveRelations;
    std::optional<std::string> geometryWkt;

    TestObject(
        StringMap attributes_,
        std::vector<TestRelation> slaveRelations_,
        std::optional<std::string> geometryWkt_ = std::nullopt)
        : attributes(std::move(attributes_))
        , slaveRelations(std::move(slaveRelations_))
        , geometryWkt(std::move(geometryWkt_))
    {}
};

struct TestRelation
{
    StringMap attributes;
    TestObject slaveObject;
};

class RevisionTest
{
public:
    RevisionTest(pgpool3::Pool& pool);

    const static size_t USE_COMPUTED_REVISION_COUNT = 0;

    void testObjects(const std::vector<TestObject>& objects,
        size_t expectedRevisionCount = USE_COMPUTED_REVISION_COUNT);

private:
    bool tryMatch(const revision::ObjectRevision& revision, const TestObject& object);
    bool tryMatch(const revision::ObjectRevision& revision, const std::vector<TestObject>& objects);
    bool tryMatch(const revision::ObjectRevision& revision, const TestRelation& relation);
    bool tryMatch(const revision::ObjectRevision& revision, const std::vector<TestRelation>& relations);

    bool tryMatch(const revision::Revisions& revisions, const TestObject& object);

    void printAttributes(std::ostream& stream, size_t indent, const StringMap& attributes);
    void printObject(std::ostream& stream, size_t indent, const revision::ObjectRevision& revision);
    void printRelation(std::ostream& stream, size_t indent, const revision::ObjectRevision& revision);
    void printGeometry(std::ostream& stream, size_t indent, const revision::ObjectRevision& revision);

    void printObject(std::ostream& stream, size_t indent, const TestObject& object);
    void printRelation(std::ostream& stream, size_t indent, const TestRelation& relation);
    void printGeometry(std::ostream& stream, size_t indent, const TestObject& object);

    pgpool3::TransactionHandle txn_;
    revision::RevisionsGateway gateway_;
    revision::Snapshot snapshot_;
};

} // namespace importer
} // namespace wiki
} // namespace maps
