#pragma once

#include <yandex/maps/mrc/signal_queue/result_queue.h>
#include <yandex/maps/mrc/signal_queue/results.h>
#include <yandex/maps/mrc/unittest/database_fixture.h>
#include <yandex/maps/mrc/unittest/local_server.h>

#include <maps/wikimap/mapspro/services/mrc/agent-proxy/yacare_servant/lib/globals.h>
#include <maps/wikimap/mapspro/services/mrc/agent-proxy/yacare_servant/lib/i18n.h>
#include <maps/wikimap/mapspro/services/mrc/agent-proxy/yacare_servant/lib/utils.h>
#include <maps/wikimap/mapspro/services/mrc/libs/fb/include/write.h>
#include <maps/wikimap/mapspro/services/mrc/libs/ugc_event_logger/include/logger.h>

#include <maps/infra/yacare/include/test_utils.h>
#include <maps/infra/yacare/include/yacare.h>

#include <maps/libs/chrono/include/days.h>
#include <maps/libs/edge_persistent_index/include/persistent_index.h>
#include <maps/libs/log8/include/log8.h>
#include <maps/libs/road_graph/include/graph.h>
#include <maps/libs/succinct_rtree/include/rtree.h>

#include <util/system/fs.h>
#include <library/cpp/geobase/lookup.hpp>
#include <library/cpp/testing/gtest/gtest.h>



namespace maps::mrc::agent_proxy::tests {

const std::string TEST_GRAPH_FOLDER = BinaryPath("maps/data/test/graph4");
const std::string TEST_PATH = BinaryPath(
    "maps/wikimap/mapspro/services/mrc/agent-proxy/yacare_servant/tests");

inline uint32_t shortId(uint64_t longId)
{
    static road_graph::PersistentIndex index{TEST_GRAPH_FOLDER +
                                             "/edges_persistent_index.fb"};
    return index.findShortId(road_graph::PersistentIndex::LongIdType{longId})
        ->value();
}

const uint32_t EDGE_1 = shortId(15981902740596952474ul);
const uint32_t EDGE_2 = shortId(3855585622174568313ul);
const uint32_t EDGE_3 = shortId(4288593032047232839ul);
const uint32_t EDGE_4 = shortId(1883205699269767843ul);
const uint32_t EDGE_5 = shortId(4041006938457847498ul);
const uint32_t EDGE_6 = shortId(13286835505597269453ul);

using PlaygroundBase = unittest::WithUnittestConfig<
    unittest::DatabaseFixture,
    unittest::MdsStubFixture
>;

class Playground: public PlaygroundBase {
public:
    static auto& instance() {
        static Playground playground;
        return playground;
    }

    road_graph::Graph roadGraph{TEST_GRAPH_FOLDER + "/road_graph.fb"};
    succinct_rtree::Rtree roadGraphRTree{TEST_GRAPH_FOLDER + "/rtree.fb", roadGraph};

    static constexpr chrono::Days ACTUAL_COVERAGE_DURATION{0};
    static constexpr chrono::Days OUTDATED_COVERAGE_DURATION{31};

    fb::GraphReader graphCoverage =
        [version = static_cast<std::string>(roadGraph.version())] {
            auto now = chrono::TimePoint::clock::now();
            auto testFile = TEST_PATH + "/graph_coverage.fb";
            fb::TGraph graph{.version = version};
            graph.edges.push_back(
                fb::TEdge{.id = EDGE_1,
                          .coverages = {fb::TEdgeCoverage{
                              .coverageFraction = 1.0,
                              .actualizationDate = now - ACTUAL_COVERAGE_DURATION}}});
            graph.edges.push_back(
                fb::TEdge{.id = EDGE_2,
                          .coverages = {fb::TEdgeCoverage{
                              .coverageFraction = 0.98,
                              .actualizationDate = now - ACTUAL_COVERAGE_DURATION}}});
            graph.edges.push_back(
                fb::TEdge{.id = EDGE_3,
                          .coverages = {fb::TEdgeCoverage{
                              .coverageFraction = 0.98,
                              .actualizationDate = now - ACTUAL_COVERAGE_DURATION}}});
            // Coverage is too old
            graph.edges.push_back(
                fb::TEdge{.id = EDGE_4,
                          .coverages = {fb::TEdgeCoverage{
                              .coverageFraction = 0.98,
                              .actualizationDate = now - OUTDATED_COVERAGE_DURATION}}});
            // Both fraction is too low and coverage is too old
            graph.edges.push_back(
                fb::TEdge{.id = EDGE_5,
                          .coverages = {fb::TEdgeCoverage{
                              .coverageFraction = 0.9,
                              .actualizationDate = now - OUTDATED_COVERAGE_DURATION}}});
            // Coverage fraction is too low
            graph.edges.push_back(
                fb::TEdge{.id = EDGE_6,
                          .coverages = {fb::TEdgeCoverage{
                              .coverageFraction = 0.5,
                              .actualizationDate = now - ACTUAL_COVERAGE_DURATION}}});
            fb::writeToFile(graph, testFile);
            return fb::GraphReader{testFile};
        }();

    NGeobase::TLookup geobaseLookup{"geodata6.bin"};
};


const std::string UGC_EVENTS_LOG = "ugc_events.log";

inline chrono::TimePoint constGetCurrentTimestamp() {
    return chrono::TimePoint(chrono::TimePoint::duration(0));
};

struct MockWikiEditorClient : IWikiEditorClient {
    MOCK_METHOD(
        PedestrianZone,
        getPedestrianZone,
        (wiki::revision::DBID),
        (const, override));

    MOCK_METHOD(
        PedestrianZone,
        savePedestrianZone,
        (const PedestrianZone&),
        (override));
};

class Fixture: public testing::Test
{
public:

    Fixture()
    : ugcEventLogger_(UGC_EVENTS_LOG, std::chrono::seconds(600), constGetCurrentTimestamp)
    {
        auto& playground = Playground::instance();
        playground.clearMds();
        auto& cfg = config();
        REQUIRE(NFs::MakeDirectoryRecursive(
                    cfg.signalsUploader().queuePath().c_str(),
                    NFs::EFilePermission::FP_COMMON_FILE),
                "Failed to create signals uploader queue directory");

        Globals::initialize(cfg,
                            playground.roadGraph,
                            playground.roadGraphRTree,
                            playground.graphCoverage,
                            playground.geobaseLookup,
                            ugcEventLogger_,
                            wikiEditorClientMock_,
                            std::make_shared<AlwaysDay>());
        initLocalization();
        yacare::setErrorReporter(errorReporter);
    }

    ~Fixture()
    {
        clearQueues();
        NFs::Remove(config().signalsUploader().queuePath().c_str());
        Playground::instance().postgres().truncateTables();
    }

    const common::Config& config() { return Playground::instance().config(); }

    pgpool3::Pool& pgPool() { return Playground::instance().pool(); }

    testing::NiceMock<MockWikiEditorClient>& wikiEditorClientMock() { return wikiEditorClientMock_; }

private:
    void clearQueues()
    {
        signal_queue::ResultsQueue queue(config().signalsUploader().queuePath());
        clearQueue<signal_queue::AssignmentImage>(queue);
        clearQueue<signal_queue::RideImage>(queue);
    }

    template <typename ResultType>
    void clearQueue(signal_queue::ResultsQueue& queue) {
        while (queue.count<ResultType>()) {
            queue.pop<ResultType>();
        }
    }

    ugc_event_logger::Logger ugcEventLogger_;
    testing::NiceMock<MockWikiEditorClient> wikiEditorClientMock_;
};


constexpr auto RECIPE_TVMTOOL_CONFIG =
        "maps/wikimap/mapspro/services/mrc/agent-proxy/yacare_servant/tests/tvmtool.recipe.conf";
class BlackboxFixture
{
    yacare::tests::UserAuthBlackboxFixture fix_;
public:
    BlackboxFixture() : fix_(RECIPE_TVMTOOL_CONFIG)
    {
    }
    std::string_view blackboxUrl() const { return fix_.blackbox().environment().url; }
};

} // namespace maps::mrc::agent_proxy::tests
