#include "../fixtures.h"
#include "../../lib/worker.h"
#include "../../lib/def.h"

#include <yandex/maps/wiki/common/aoi.h>
#include <yandex/maps/wiki/common/robot.h>
#include <yandex/maps/wiki/social/feedback/agent.h>
#include <yandex/maps/wiki/social/feedback/attribute_names.h>
#include <yandex/maps/wiki/social/feedback/description.h>
#include <yandex/maps/wiki/social/feedback/description_producers.h>
#include <yandex/maps/wiki/social/feedback/gateway_rw.h>
#include <yandex/maps/wiki/social/feedback/mv_source_type.h>
#include <yandex/maps/wiki/social/feedback/task.h>
#include <yandex/maps/wiki/social/feedback/partition.h>
#include <yandex/maps/wiki/social/feedback/task_filter.h>
#include <yandex/maps/wiki/social/feedback/task_patch.h>

#include <yandex/maps/wiki/unittest/arcadia.h>
#include <library/cpp/testing/unittest/registar.h>

#include <algorithm>
#include <set>

namespace maps::wiki::schedule_feedback::tests{

namespace sf = social::feedback;
namespace sfa = social::feedback::attrs;

namespace {

const social::TUid USER_ID = 1;

const std::string AOI_ID = "aoi_id";
const std::string FEEDBACK_TASK_ID = "feedback_task_id";
const std::string SOME_SOURCE = "some-source";

auto SOME_DESCR()
{
    return sf::Description("some-description");
}

sf::Tasks getTasksFromOutgoingBucket(pgpool3::Pool& pool)
{
    auto txn = pool.masterReadOnlyTransaction();
    return sf::GatewayRO(txn.get()).tasksByFilter(
            sf::TaskFilter().bucket(sf::Bucket::Outgoing));
}

sf::Tasks getTasksAcquiredByUid(pgpool3::Pool& pool, social::TUid uid)
{
    auto txn = pool.masterReadOnlyTransaction();
    return sf::GatewayRO(txn.get()).tasksByFilter(
            sf::TaskFilter().acquiredBy(uid));
}

const std::string SOURCE_NAVI = "navi";

struct AoiFeedback
{
    TId aoiId;
    TId feedbackTaskId;
};

bool operator == (const AoiFeedback& lhs, const AoiFeedback& rhs)
{
    return lhs.aoiId == rhs.aoiId &&
           lhs.feedbackTaskId == rhs.feedbackTaskId;
}

std::vector<AoiFeedback> selectAllFromAoiFeed(
    pqxx::transaction_base& socialTxn,
    sf::Partition partition)
{
    std::stringstream query;
    query <<
        " SELECT " <<
            AOI_ID << "," << FEEDBACK_TASK_ID <<
        " FROM " <<
            sf::aoiFeedbackFeedPartitionTable(partition);

    std::vector<AoiFeedback> res;
    for (const auto& row : socialTxn.exec(query.str())) {
        res.push_back(AoiFeedback{
            row[AOI_ID].as<TId>(),
            row[FEEDBACK_TASK_ID].as<TId>()
        });
    }
    return res;
}

} // anonymous

Y_UNIT_TEST_SUITE(worker_tests) {

Y_UNIT_TEST_F(import_routine, DBFixture)
{
    tasks::StatusWriter statusWriter(std::nullopt);
    Worker worker(pool(), pool(), pool(), statusWriter);
    social::TId task0Id = 0;
    social::TId task1Id = 0;

    {
        auto txn = pool().masterWriteableTransaction();
        auto task = sf::GatewayRW(txn.get()).addTask(
            USER_ID,
            sf::TaskNew(
                geolib3::Point2(0., 0.),
                sf::Type::Barrier,
                SOME_SOURCE,
                SOME_DESCR()
            )
        );
        txn->commit();
        task0Id = task.id();
    }

    UNIT_ASSERT_VALUES_EQUAL(getTasksFromOutgoingBucket(pool()).size(), 0);

    worker.doTask();

    UNIT_ASSERT_VALUES_EQUAL(getTasksFromOutgoingBucket(pool()).size(), 1);

    {
        auto txn = pool().masterWriteableTransaction();
        auto task = sf::GatewayRW(txn.get()).addTask(
            USER_ID,
            sf::TaskNew(
                geolib3::Point2(0., 0.),
                sf::Type::Address,
                SOME_SOURCE,
                SOME_DESCR()
            )
        );
        txn->commit();
        task1Id = task.id();
    }

    UNIT_ASSERT_VALUES_EQUAL(getTasksFromOutgoingBucket(pool()).size(), 1);

    worker.doTask();

    UNIT_ASSERT_VALUES_EQUAL(getTasksFromOutgoingBucket(pool()).size(), 2);
}

Y_UNIT_TEST_F(release_overdue, DBFixture)
{
    tasks::StatusWriter statusWriter(std::nullopt);
    Worker worker(pool(), pool(), pool(), statusWriter);
    social::TId taskId = 0;

    {
        auto txn = pool().masterWriteableTransaction();
        sf::Agent agent(txn.get(), USER_ID);
        auto task = agent.addTask(
            sf::TaskNew(
                geolib3::Point2(0., 0.),
                sf::Type::Address,
                SOME_SOURCE,
                SOME_DESCR()
            )
        );
        taskId = task.id();
        auto revealedTask = agent.revealTaskByIdCascade(taskId);
        ASSERT(revealedTask);
        agent.acquireTask(*revealedTask);
        txn->commit();
    }

    UNIT_ASSERT_VALUES_EQUAL(getTasksAcquiredByUid(pool(), USER_ID).size(), 1);

    worker.doTask();
    UNIT_ASSERT_VALUES_EQUAL(getTasksAcquiredByUid(pool(), USER_ID).size(), 1);

    {
        auto txn = pool().masterWriteableTransaction();
        sf::GatewayRW(txn.get()).updateTaskById(
            taskId,
            sf::TaskPatch(USER_ID).setAcquired(
                std::chrono::system_clock::now() - std::chrono::minutes(61)));
        txn->commit();
    }

    worker.doTask();
    UNIT_ASSERT_VALUES_EQUAL(getTasksAcquiredByUid(pool(), USER_ID).size(), 0);
}

Y_UNIT_TEST_F(aoi_feed, DBFixture)
{
    const TId AOI_ID = 10;

    // add aoi region
    {
        const TId ANY_COMMIT_ID = 1;
        const double ANY_AREA = 1;

        auto viewTrunkWriteTxn = pool().masterWriteableTransaction();

        common::AoiParams aoiParams;
        aoiParams.aoiId = AOI_ID;
        aoiParams.commitId = ANY_COMMIT_ID;
        aoiParams.name = "smth";
        aoiParams.type = 0;
        aoiParams.area = ANY_AREA;
        aoiParams.polygonMerc = geolib3::Polygon2(
            {{0, 0}, {0, 10}, {10, 10}, {10, 0}}
        );

        common::addAoiRegionToViewTrunk(aoiParams, viewTrunkWriteTxn.get());

        viewTrunkWriteTxn->commit();
    }

    // add task
    TId taskId;
    {
        auto sociaWriteTxn = pool().masterWriteableTransaction();
        auto task = sf::GatewayRW(sociaWriteTxn.get()).addTask(
            USER_ID,
            sf::TaskNew(
                geolib3::Point2(5, 5),
                sf::Type::Barrier,
                SOME_SOURCE,
                SOME_DESCR()
            )
        );
        sociaWriteTxn->commit();
        taskId = task.id();
    }

    // 1) add task to pending feed
    // 2) task: from incoming -> outgoing opened
    //    feed: from incoming -> outgoing opened
    //
    {
        tasks::StatusWriter statusWriter(std::nullopt);
        Worker worker(pool(), pool(), pool(), statusWriter);
        worker.doTask();
    }

    auto sociaReadTxn = pool().slaveTransaction();

    auto feedPending = selectAllFromAoiFeed(
        sociaReadTxn.get(), sf::Partition::Pending);
    auto feedOutgoingOpened = selectAllFromAoiFeed(
        sociaReadTxn.get(), sf::Partition::OutgoingOpened);
    auto feedOutgoingClosed = selectAllFromAoiFeed(
        sociaReadTxn.get(), sf::Partition::OutgoingClosed);

    UNIT_ASSERT(feedPending.empty());

    UNIT_ASSERT(feedOutgoingOpened.size() == 1);
    UNIT_ASSERT((feedOutgoingOpened.front() == AoiFeedback{AOI_ID, taskId}));

    UNIT_ASSERT(feedOutgoingClosed.empty());
}

} // Y_UNIT_TEST_SUITE(worker_tests)

} // namespace maps::wiki::schedule_feedback::tests
