#pragma once

#include "eye/hypothesis_attrs.h"
#include "feature.h"
#include "ride.h"

#include <maps/libs/common/include/exception.h>
#include <util/generic/overloaded.h>

#include <boost/lexical_cast.hpp>

namespace maps::mrc::db {

template <class T>
void requireEquals(const T& entity,
                   const std::string& userId,
                   const std::string& sourceId,
                   const std::optional<std::string>& clientRideId)
{
    static const auto getDescription = TOverloaded{
        [](const db::Feature& feature) {
            return "db::Feature{.id=" + std::to_string(feature.id()) + "}";
        },
        [](const db::Ride& ride) {
            return "db::Ride{.rideId=" + std::to_string(ride.rideId()) + "}";
        },
        [](const db::DeletedInterval& deletedInterval) {
            return "db::DeletedInterval{.id=" +
                   std::to_string(deletedInterval.id()) + "}";
        },
    };

    static const auto getUserId = TOverloaded{
        [](const auto& entity) { return entity.userId(); },
        [](const db::Feature& feature) {
            return feature.userId().value_or("");
        },
    };

    static const auto getClientRideId = TOverloaded{
        [](const auto& entity) { return entity.clientRideId(); },
        [](const db::Ride& ride) { return ride.clientId(); },
    };

    REQUIRE(getUserId(entity) == userId,
            getDescription(entity)
                << ": .userId=" << getUserId(entity) << " != " << userId);
    REQUIRE(entity.sourceId() == sourceId,
            getDescription(entity)
                << ": .sourceId=" << entity.sourceId() << " != " << sourceId);
    REQUIRE(getClientRideId(entity) == clientRideId,
            getDescription(entity)
                << ": .clientRideId=" << getClientRideId(entity).value_or("")
                << " != " << clientRideId.value_or(""));
}

template <class T>
void requireEquals(const std::vector<T>& entities,
                   const std::string& userId,
                   const std::string& sourceId,
                   const std::optional<std::string>& clientRideId)
{
    for (const auto& entity : entities) {
        requireEquals(entity, userId, sourceId, clientRideId);
    }
}

using FeedbackIdToHypothesisTypeMap =
    std::unordered_map<TId, eye::HypothesisType>;

FeedbackIdToHypothesisTypeMap loadRideHypotheses(sql_chemistry::Transaction&,
                                                 const Ride&);

template <class Proto>
void copyRideHypotheses(const FeedbackIdToHypothesisTypeMap& hypotheses,
                        Proto& proto)
{
    for (const auto& [feedbackId, hypothesisType] : hypotheses) {
        auto* hypothesis = proto.add_hypotheses();
        auto* type = hypothesis->mutable_type();
        switch (hypothesisType) {
            case eye::HypothesisType::AbsentTrafficLight:
                type->mutable_absent_traffic_light();
                break;
            case eye::HypothesisType::AbsentHouseNumber:
                type->mutable_absent_house_number();
                break;
            case eye::HypothesisType::WrongSpeedLimit:
                type->mutable_wrong_speed_limit();
                break;
            case eye::HypothesisType::AbsentParking:
                type->mutable_absent_parking();
                break;
            case eye::HypothesisType::WrongParkingFtType:
                type->mutable_wrong_parking_ft_type();
                break;
            case eye::HypothesisType::TrafficSign:
                type->mutable_traffic_sign();
                break;
            case eye::HypothesisType::WrongDirection:
                type->mutable_wrong_direction();
                break;
            case eye::HypothesisType::ProhibitedPath:
                type->mutable_prohibited_path();
                break;
            case eye::HypothesisType::LaneHypothesis:
                type->mutable_lane_hypothesis();
                break;
            case eye::HypothesisType::SpeedBump:
                type->mutable_speed_bump();
                break;
            case eye::HypothesisType::RailwayCrossing:
                type->mutable_railway_crossing();
                break;
        }
        hypothesis->set_feedback_task_id(std::to_string(feedbackId));
    }
}

template <class Proto>
FeedbackIdToHypothesisTypeMap getRideHypotheses(const Proto& proto)
{
    static auto getHypothesisType = [](const auto& typeCase) {
        switch (typeCase) {
            case Proto::Hypothesis::Type::kAbsentTrafficLight:
                return eye::HypothesisType::AbsentTrafficLight;
            case Proto::Hypothesis::Type::kAbsentHouseNumber:
                return eye::HypothesisType::AbsentHouseNumber;
            case Proto::Hypothesis::Type::kWrongSpeedLimit:
                return eye::HypothesisType::WrongSpeedLimit;
            case Proto::Hypothesis::Type::kAbsentParking:
                return eye::HypothesisType::AbsentParking;
            case Proto::Hypothesis::Type::kWrongParkingFtType:
                return eye::HypothesisType::WrongParkingFtType;
            case Proto::Hypothesis::Type::kTrafficSign:
                return eye::HypothesisType::TrafficSign;
            case Proto::Hypothesis::Type::kWrongDirection:
                return eye::HypothesisType::WrongDirection;
            case Proto::Hypothesis::Type::kProhibitedPath:
                return eye::HypothesisType::ProhibitedPath;
            case Proto::Hypothesis::Type::kLaneHypothesis:
                return eye::HypothesisType::LaneHypothesis;
            default:
                REQUIRE(false, "unexpected type case: " << typeCase);
        }
    };
    auto result = FeedbackIdToHypothesisTypeMap{};
    for (const auto& hypothesis : proto.hypotheses()) {
        if (!hypothesis.has_feedback_task_id()) {
            continue;
        }
        auto feedbackId =
            boost::lexical_cast<TId>(hypothesis.feedback_task_id());
        auto hypothesisType = getHypothesisType(hypothesis.type().type_case());
        result.insert({feedbackId, hypothesisType});
    }
    return result;
}

}  // namespace maps::mrc::db
