#include <maps/wikimap/mapspro/services/mrc/libs/db/tests/eye/common.h>

#include <library/cpp/testing/gtest/gtest.h>
#include <maps/libs/common/include/exception.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/hypothesis_attrs.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/hypothesis_gateway.h>
#include <maps/libs/geolib/include/test_tools/comparison.h>

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

using Fixture = db::tests::Fixture;

TEST(eye_hypothesis, make_absent_traffic_light_attrs)
{
    const wiki::revision::RevisionID revisionId{1, 2};
    const AbsentTrafficLightAttrs attrs{revisionId};

    const auto expected = json::Value(json::repr::ObjectRepr{
        {"object_id", json::Value(1u)},
        {"commit_id", json::Value(2u)},
    });


    EXPECT_EQ(toJson(attrs), expected);

    EXPECT_EQ(attrs.junctionRevisionId.objectId(), revisionId.objectId());
    EXPECT_EQ(attrs.junctionRevisionId.commitId(), revisionId.commitId());
}

TEST(eye_hypothesis, make_traffic_sign_attrs)
{
    const auto type = wiki::social::feedback::Type::OneWayTrafficSign;
    const TrafficSignAttrs attrs{type};

    const auto expected = json::Value(json::repr::ObjectRepr{
        {"feedback_type", json::Value("one-way-traffic-sign")},
    });

    EXPECT_EQ(toJson(attrs), expected);
    EXPECT_EQ(attrs.feedbackType, type);
}

TEST(eye_hypothesis, make_lane_hypothesis_attrs)
{
    const wiki::revision::RevisionID revisionId{1, 2};
    const LaneHypothesisAttrs attrs{revisionId};

    const auto expected = json::Value(json::repr::ObjectRepr{
        {"object_id", json::Value(1u)},
        {"commit_id", json::Value(2u)},
    });

    EXPECT_EQ(toJson(attrs), expected);
    EXPECT_EQ(attrs.roadElementRevisionId.objectId(), revisionId.objectId());
    EXPECT_EQ(attrs.roadElementRevisionId.commitId(), revisionId.commitId());
}

TEST(eye_hypothesis, make_wrong_speed_limit_attrs)
{
    const wiki::revision::RevisionID revisionId{1, 2};
    int correctSpeedLimit = 5;
    int currentSpeedLimit = 15;
    const WrongSpeedLimitAttrs attrs{
        revisionId,
        correctSpeedLimit, currentSpeedLimit};

    const auto expected = json::Value(json::repr::ObjectRepr{
        {"correct_speed_limit", json::Value(5)},
        {"current_speed_limit", json::Value(15)},
        {"object_id", json::Value(1u)},
        {"commit_id", json::Value(2u)},
    });

    EXPECT_EQ(toJson(attrs), expected);
    EXPECT_EQ(attrs.correctSpeedLimit, correctSpeedLimit);
    EXPECT_EQ(attrs.currentSpeedLimit.value(), currentSpeedLimit);
    EXPECT_EQ(attrs.roadElementRevisionId.objectId(), revisionId.objectId());
    EXPECT_EQ(attrs.roadElementRevisionId.commitId(), revisionId.commitId());
}

TEST(eye_hypothesis, make_wrong_direction_attrs)
{
    const wiki::revision::RevisionID revisionId{1, 2};
    object::RoadElement::Direction supposedDirection = object::RoadElement::Direction::Forward;
    object::RoadElement::Direction currentDirection = object::RoadElement::Direction::Backward;

    const WrongDirectionAttrs attrs{
        revisionId,
        supposedDirection, currentDirection};

    const auto expected = json::Value(json::repr::ObjectRepr{
        {"supposed_direction", json::Value("F")},
        {"current_direction", json::Value("T")},
        {"object_id", json::Value(1u)},
        {"commit_id", json::Value(2u)},
    });

    EXPECT_EQ(toJson(attrs), expected);
    EXPECT_EQ(attrs.supposedDirection, supposedDirection);
    EXPECT_EQ(attrs.currentDirection, currentDirection);
    EXPECT_EQ(attrs.roadElementRevisionId.objectId(), revisionId.objectId());
    EXPECT_EQ(attrs.roadElementRevisionId.commitId(), revisionId.commitId());
}

TEST(eye_hypothesis, make_absent_parking_attrs)
{
    const wiki::revision::RevisionID revisionId{1, 2};
    const bool isToll = true;

    const AbsentParkingAttrs attrs{
        revisionId,
        isToll};

    const auto expected = json::Value(json::repr::ObjectRepr{
        {"is_toll", json::Value(isToll)},
        {"object_id", json::Value(1u)},
        {"commit_id", json::Value(2u)},
    });

    EXPECT_EQ(toJson(attrs), expected);
    EXPECT_EQ(attrs.isToll, isToll);
    EXPECT_EQ(attrs.parkingRevisionId.objectId(), revisionId.objectId());
    EXPECT_EQ(attrs.parkingRevisionId.commitId(), revisionId.commitId());
}

TEST(eye_hypothesis, make_wrong_parking_ft_type_attrs)
{
    const wiki::revision::RevisionID revisionId{1, 2};
    ymapsdf::ft::Type correctFtType = ymapsdf::ft::Type::UrbanStructure;
    ymapsdf::ft::Type currentFtType = ymapsdf::ft::Type::UrbanEdu;

    const WrongParkingFtTypeAttrs attrs{
        revisionId,
        correctFtType, currentFtType};

    const auto expected = json::Value(json::repr::ObjectRepr{
        {"correct_ft_type", json::Value(106)},
        {"current_ft_type", json::Value(171)},
        {"object_id", json::Value(1u)},
        {"commit_id", json::Value(2u)},
    });

    EXPECT_EQ(toJson(attrs), expected);
    EXPECT_EQ(attrs.correctFtType, correctFtType);
    EXPECT_EQ(attrs.currentFtType, currentFtType);
    EXPECT_EQ(attrs.parkingRevisionId.objectId(), revisionId.objectId());
    EXPECT_EQ(attrs.parkingRevisionId.commitId(), revisionId.commitId());
}

TEST(eye_hypothesis, make_prohibited_path_attrs)
{
    const wiki::social::feedback::Movement movement
        = wiki::social::feedback::Movement::Forward;
    const geolib3::Polyline2 mercatorPath(
        geolib3::PointsVector({{0., 0.}, {1., 1.}})
    );

    const ProhibitedPathAttrs attrs{movement, mercatorPath};

    const auto expected = json::Value::fromString(R"(
        {
          "movement" : "forward",
          "mercator_path" : {
              "type" : "LineString",
              "coordinates" : [[0.0, 0.0], [1.0, 1.0]]
          }
        }
    )");

    EXPECT_EQ(toJson(attrs), expected);
    EXPECT_EQ(attrs.movement, movement);
    EXPECT_TRUE(
        geolib3::test_tools::approximateEqual(
            attrs.mercatorPath, mercatorPath, geolib3::EPS)
    );
}

TEST(eye_hypothesis, make_absent_house_number_attrs)
{
    const std::string number = "11";
    const AbsentHouseNumberAttrs attrs{number};

    const auto expected = json::Value::fromString(R"(
        {
          "number" : "11"
        }
    )");


    EXPECT_EQ(toJson(attrs), expected);

    EXPECT_EQ(attrs.number, number);
}

TEST(eye_hypothesis, make_speed_bump_attrs)
{
    const SpeedBumpAttrs attrs{
        .objectRevisionId = wiki::revision::RevisionID{1, 2},
        .isAhead = true
    };

    const auto expected = json::Value::fromString(R"(
        {
            "object_id": 1,
            "commit_id": 2,
            "is_ahead": true
        }
    )");


    EXPECT_EQ(toJson(attrs), expected);

    SpeedBumpAttrs loadedAttrs = SpeedBumpAttrs::fromJson(expected);

    EXPECT_EQ(loadedAttrs.objectRevisionId.objectId(), attrs.objectRevisionId.objectId());
    EXPECT_EQ(loadedAttrs.objectRevisionId.commitId(), attrs.objectRevisionId.commitId());
    EXPECT_EQ(loadedAttrs.isAhead, attrs.isAhead);
}

TEST_F(Fixture, insert_speed_bump)
{
    const SpeedBumpAttrs attrs{
        .objectRevisionId = wiki::revision::RevisionID{1, 2},
        .isAhead = true
    };

    Hypotheses hypotheses {
        {
            geolib3::Point2(100, 200),
            attrs
        },
    };

    auto txn = txnHandle();
    db::eye::HypothesisGateway(*txn).insertx(hypotheses);
    txn->commit();
}

TEST(eye_hypothesis, make_railway_crossing_attrs)
{
    const RailwayCrossingAttrs attrs{
        .objectRevisionId = wiki::revision::RevisionID{1, 2},
        .hasBarrier = true
    };

    const auto expected = json::Value::fromString(R"(
        {
            "object_id": 1,
            "commit_id": 2,
            "has_barrier": true
        }
    )");


    EXPECT_EQ(toJson(attrs), expected);

    RailwayCrossingAttrs loadedAttrs = RailwayCrossingAttrs::fromJson(expected);

    EXPECT_EQ(loadedAttrs.objectRevisionId.objectId(), attrs.objectRevisionId.objectId());
    EXPECT_EQ(loadedAttrs.objectRevisionId.commitId(), attrs.objectRevisionId.commitId());
    EXPECT_EQ(loadedAttrs.hasBarrier, attrs.hasBarrier);
}

TEST_F(Fixture, insert_railway_crossing)
{
    const RailwayCrossingAttrs attrs{
        .objectRevisionId = wiki::revision::RevisionID{1, 2},
        .hasBarrier = true
    };

    Hypotheses hypotheses {
        {
            geolib3::Point2(100, 200),
            attrs
        },
    };

    auto txn = txnHandle();
    db::eye::HypothesisGateway(*txn).insertx(hypotheses);
    txn->commit();
}

TEST_F(Fixture, insert_hypothesis)
{
    const wiki::revision::RevisionID revisionId{1, 2};
    const std::string number = "11";
    const AbsentTrafficLightAttrs absentTrafficLightAttrs{revisionId};
    const AbsentHouseNumberAttrs absentHouseNumberAttrs{number};
    const geolib3::Point2 mercatorPos1(100, 200);
    const geolib3::Point2 mercatorPos2(200, 300);

    Hypotheses hypotheses {
        {
            mercatorPos1,
            absentTrafficLightAttrs
        },
        {
            mercatorPos2,
            absentHouseNumberAttrs
        },
    };

    auto txn = txnHandle();
    const TId txnId = HypothesisGateway(*txn).insertx(hypotheses);
    txn->commit();

    {   // check first
        const auto hypothesis = HypothesisGateway(*txnHandle()).loadById(hypotheses[0].id());

        EXPECT_EQ(hypothesis.txnId(), txnId);
        EXPECT_EQ(hypothesis.deleted(), false);
        EXPECT_EQ(hypothesis.type(), HypothesisType::AbsentTrafficLight);

        const auto expected = json::Value(json::repr::ObjectRepr{
            {"object_id", json::Value(1u)},
            {"commit_id", json::Value(2u)},
        });

        EXPECT_EQ(toJson(hypothesis.attrs<AbsentTrafficLightAttrs>()), expected);
    }

    {   // check second
        const auto hypothesis = HypothesisGateway(*txnHandle()).loadById(hypotheses[1].id());

        EXPECT_EQ(hypothesis.txnId(), txnId);
        EXPECT_EQ(hypothesis.deleted(), false);
        EXPECT_EQ(hypothesis.type(), HypothesisType::AbsentHouseNumber);

        const auto expected = json::Value(json::repr::ObjectRepr{
            {"number", json::Value("11")},
        });
        EXPECT_EQ(toJson(hypothesis.attrs<AbsentHouseNumberAttrs>()), expected);
    }
}

TEST_F(Fixture, update_hypothesis)
{
    const std::string number = "11";
    const AbsentHouseNumberAttrs absentHouseNumberAttrs{number};
    const geolib3::Point2 mercatorPos(100, 200);

    Hypothesis hypothesis(mercatorPos, absentHouseNumberAttrs);

    TId txnId;
    {
        auto txn = txnHandle();
        txnId = HypothesisGateway(*txn).insertx(hypothesis);
        txn->commit();
    }

    TId updatedTxnId;
    {
        auto txn = txnHandle();
        hypothesis.setDeleted(true);
        updatedTxnId = HypothesisGateway(*txn).updatex(hypothesis);
        txn->commit();
    }

    {    // Check value
        const auto updated = HypothesisGateway(*txnHandle()).loadById(hypothesis.id());

        EXPECT_TRUE(txnId < updatedTxnId);
        EXPECT_EQ(updated.txnId(), updatedTxnId);
        EXPECT_EQ(updated.deleted(), true);
    }
}

TEST_F(HypothesisFixture, insert_hypothesis_object)
{
    auto txn = txnHandle();

    HypothesisObjects hypothesisObjects {
        {
            hypotheses[0].id(),
            objects[0].id()
        },
        {
            hypotheses[1].id(),
            objects[3].id()
        },
    };

    const TId txnId = HypothesisObjectGateway(*txn).insertx(hypothesisObjects);

    txn->commit();

    {   // first
        auto txn = txnHandle();
        const auto hypothesisObject = HypothesisObjectGateway(*txn).loadById(hypothesisObjects[0].id());
        EXPECT_EQ(hypothesisObject.txnId(), txnId);
        EXPECT_EQ(hypothesisObject.hypothesisId(), hypotheses[0].id());
        EXPECT_EQ(hypothesisObject.objectId(), objects[0].id());
        EXPECT_EQ(hypothesisObject.deleted(), false);
    }

    {   // second
        auto txn = txnHandle();
        const auto hypothesisObject = HypothesisObjectGateway(*txn).loadById(hypothesisObjects[1].id());
        EXPECT_EQ(hypothesisObject.txnId(), txnId);
        EXPECT_EQ(hypothesisObject.hypothesisId(), hypotheses[1].id());
        EXPECT_EQ(hypothesisObject.objectId(), objects[3].id());
        EXPECT_EQ(hypothesisObject.deleted(), false);
    }
}

TEST_F(HypothesisFixture, unique_hypothesis_object)
{
    auto txn = txnHandle();
    HypothesisObjects hypothesisObjects {
        {hypotheses[0].id(), objects[0].id()},
        {hypotheses[0].id(), objects[0].id()},
    };
    EXPECT_THROW(
        HypothesisObjectGateway(*txn).insertx(hypothesisObjects),
        maps::sql_chemistry::UniqueViolationError
    );
}

TEST_F(HypothesisFixture, update_hypothesis_object)
{
    HypothesisObject hypothesisObject {
        hypotheses[0].id(), objects[0].id()
    };

    auto txn = txnHandle();
    TId txnId = HypothesisObjectGateway(*txn).insertx(hypothesisObject);
    txn->commit();

    TId updatedTxnId;
    {   // update
        auto txn = txnHandle();
        hypothesisObject.setDeleted(true);
        updatedTxnId = HypothesisObjectGateway(*txn).updatex(hypothesisObject);
        txn->commit();
    }

    {
        auto txn = txnHandle();
        const auto loadedHypothesisObject = HypothesisObjectGateway(*txn).loadById(hypothesisObject.id());

        EXPECT_TRUE(txnId < updatedTxnId);
        EXPECT_EQ(loadedHypothesisObject.txnId(), updatedTxnId);
        EXPECT_EQ(loadedHypothesisObject.hypothesisId(), hypotheses[0].id());
        EXPECT_EQ(loadedHypothesisObject.objectId(), objects[0].id());
    }
}

TEST_F(HypothesisFixture, insert_hypothesis_feedback)
{
    const db::TId feedbackId = 123u;

    auto txn = txnHandle();

    HypothesisFeedback hypothesisFeedback {
        hypotheses[0].id(),
        123u
    };

    const TId txnId = HypothesisFeedbackGateway(*txn).insertx(hypothesisFeedback);

    txn->commit();

    {
        auto txn = txnHandle();
        const auto hypothesisFeedbacks = HypothesisFeedbackGateway(*txn).load(
            db::eye::table::HypothesisFeedback::feedbackId == feedbackId
        );
        ASSERT_EQ(hypothesisFeedbacks.size(), 1u);
        const auto& hypothesisFeedback = hypothesisFeedbacks[0];

        EXPECT_EQ(hypothesisFeedback.txnId(), txnId);
        EXPECT_EQ(hypothesisFeedback.hypothesisId(), hypotheses[0].id());
        EXPECT_EQ(hypothesisFeedback.feedbackId(), feedbackId);
    }
}

TEST_F(HypothesisFixture, unique_hypothesis_feedback)
{
    const db::TId feedbackId = 123u;

    auto txn = txnHandle();
    HypothesisFeedbacks hypothesisFeedbacks {
        {hypotheses[0].id(), feedbackId},
        {hypotheses[0].id(), feedbackId},
    };
    EXPECT_THROW(
        HypothesisFeedbackGateway(*txn).insertx(hypothesisFeedbacks),
        maps::sql_chemistry::UniqueViolationError
    );
}

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