#pragma once

#include "common.h"
#include "feature_transaction.h"
#include "feature.h"
#include "table_traits.h"

#include <maps/libs/sql_chemistry/include/columns/array.h>
#include <maps/libs/sql_chemistry/include/gateway.h>
#include <maps/libs/sql_chemistry/include/system_information.h>

namespace maps::mrc::db {
namespace table {
using namespace sql_chemistry;

struct Feature : Table<db::Feature> {
    static constexpr std::string_view name_{"signals.feature"sv};

    static constexpr BigSerialKey id{"feature_id"sv, name_};
    static constexpr StringColumn sourceId{"source_id"sv, name_};
    static constexpr NullableMercatorColumn<geolib3::Point2> pos{"pos"sv, name_};
    static constexpr NullableDoubleColumn heading{"heading"sv, name_};
    static constexpr TimePointColumn date{"date"sv, name_};
    static constexpr StringColumn mdsGroupId{"mds_group_id"sv, name_};
    static constexpr StringColumn mdsPath{"mds_path"sv, name_};
    static constexpr NullableNumericColumn<size_t> width{"width"sv, name_};
    static constexpr NullableNumericColumn<size_t> height{"height"sv, name_};
    static constexpr NullableDoubleColumn quality{"quality"sv, name_};
    static constexpr NullableDoubleColumn roadProbability{"road_probability"sv, name_};
    static constexpr NullableDoubleColumn forbiddenProbability{"forbidden_probability"sv, name_};
    static constexpr EnumColumn<Dataset> dataset{"dataset"sv, name_};
    static constexpr BooleanColumn isPublished{"is_published"sv, name_}; // default false
    static constexpr NullableBooleanColumn shouldBePublished{"should_be_published"sv, name_};
    static constexpr NullableNumericColumn<int16_t> orientation{"orientation"sv, name_};
    static constexpr NullableNumericColumn<int16_t> cameraDeviation{
        "camera_deviation"sv, name_};
    static constexpr NullableEnumColumn<FeaturePrivacy> privacy{"privacy"sv, name_};
    static constexpr NullableMercatorColumn<geolib3::Point2> odometerPos{"odometer_pos"sv, name_};
    static constexpr Nullable<NumericArrayColumn<double>> cameraOrientationRodrigues{
      "camera_orientation_rodrigues"sv, name_};
    static constexpr NullableEnumColumn<GraphType> graph{"graph"sv, name_};
    static constexpr NullableTimePointColumn uploadedAt{"uploaded_at"sv, name_};
    static constexpr NullableBooleanColumn moderatorsShouldBePublished{
        "moderators_should_be_published"sv, name_};
    static constexpr NullableNumericColumn<TId> assignmentId{"assignment_id"sv, name_};
    static constexpr NullableStringColumn userId{"user_id"sv, name_};
    static constexpr NullableBooleanColumn gdprDeleted{"gdpr_deleted"sv, name_};
    static constexpr NullableBooleanColumn showAuthorship{"show_authorship"sv, name_};
    static constexpr NullableBooleanColumn deletedByUser{"deleted_by_user"sv, name_};
    static constexpr NullableNumericColumn<TId> walkObjectId{"walk_object_id"sv, name_};
    static constexpr NullableBooleanColumn automaticShouldBePublished{
        "automatic_should_be_published"sv, name_};
    static constexpr NullableTimePointColumn processedAt{"processed_at"sv, name_};
    static constexpr NullableStringColumn clientRideId{"client_ride_id"sv, name_};
    static constexpr NullableBooleanColumn deletedFromMds{"deleted_from_mds"sv, name_};
    static constexpr XminColumn xmin{name_};

    static constexpr auto columns_() {
        return std::tie(id,
                        sourceId,
                        pos,
                        heading,
                        date,
                        mdsGroupId,
                        mdsPath,
                        width,
                        height,
                        quality,
                        roadProbability,
                        forbiddenProbability,
                        dataset,
                        isPublished,
                        shouldBePublished,
                        orientation,
                        cameraDeviation,
                        privacy,
                        odometerPos,
                        cameraOrientationRodrigues,
                        graph,
                        uploadedAt,
                        moderatorsShouldBePublished,
                        assignmentId,
                        userId,
                        gdprDeleted,
                        showAuthorship,
                        deletedByUser,
                        walkObjectId,
                        automaticShouldBePublished,
                        processedAt,
                        clientRideId,
                        deletedFromMds,
                        xmin);
    }
};

struct FeatureQaTask : Table<db::FeatureQaTask> {
    static constexpr std::string_view name_{"signals.feature_qa_task"sv};

    // not null PRIMARY KEY
    static constexpr NumericColumn<TId> featureId{"feature_id"sv, name_};
    // not null FOREIGN PRIMARY KEY toloka_mgr.task
    static constexpr NumericColumn<TId> tolokaId{"toloka_id"sv, name_};

    static constexpr auto columns_() { return std::tie(featureId, tolokaId); }
};

struct FeatureTransaction : Table<db::FeatureTransaction> {
    static constexpr std::string_view name_{"signals.feature_transaction"sv};

    static constexpr Int64PrimaryKey featureId{"feature_id"sv, name_};
    static constexpr NumericColumn<TId> transactionId{"transaction_id"sv, name_};

    static constexpr auto columns_() { return std::tie(featureId, transactionId); }
};

} // namespace table

using FeatureTransactionGateway = sql_chemistry::Gateway<table::FeatureTransaction>;

inline
void updateFeatureTransaction(const db::Feature& feature, pqxx::transaction_base& txn) {
    TId txnId = sql_chemistry::SystemInformation(txn).getCurrentTransactionId();
    FeatureTransaction featureTransaction{feature.id(), txnId};
    FeatureTransactionGateway{txn}.upsert(featureTransaction);
}

inline
void updateFeaturesTransaction(TArrayRef<const db::Feature> features, pqxx::transaction_base& txn)
{
    TId txnId = sql_chemistry::SystemInformation(txn).getCurrentTransactionId();

    FeatureTransactions featureTransactions;
    featureTransactions.reserve(features.size());
    for (const auto& feature : features) {
        featureTransactions.emplace_back(feature.id(), txnId);
    }
    FeatureTransactionGateway{txn}.upsert(featureTransactions);
}

enum class UpdateFeatureTxn { Yes, No };

class FeatureGateway : public sql_chemistry::Gateway<table::Feature> {
public:
    using Tb = table::Feature;
    using Base = sql_chemistry::Gateway<table::Feature>;
    using Base::Base;

    void update(db::Feature& feature, UpdateFeatureTxn updateFeatureTxn) {
        Base::update(feature);
        if (updateFeatureTxn == UpdateFeatureTxn::Yes) {
            updateFeatureTransaction(feature, this->txn());
        }
    }

    void update(TArrayRef<db::Feature> features, UpdateFeatureTxn updateFeatureTxn) {
        Base::update(features);
        if (updateFeatureTxn == UpdateFeatureTxn::Yes) {
            updateFeaturesTransaction(features, this->txn());
        }
    }

    template <class... Ts>
    void upsert(Ts&& ...) = delete;

    /// Load photos taken chronologically by the same camera.
    /// Result is empty when base feature isn't found or not published.
    /// @param baseFeatureId - photo that defines the camera and the time
    /// @param beforeLimit - number of shots taken before
    /// @param afterLimit - number of shots taken after
    Entities loadSequence(TId baseFeatureId, size_t beforeLimit, size_t afterLimit);

    std::vector<std::string> loadAllSources();
};

using FeatureQaTaskGateway = sql_chemistry::Gateway<table::FeatureQaTask>;

TABLE_TRAITS(Feature, table::Feature);

TABLE_TRAITS(FeatureQaTask, table::FeatureQaTask);

inline sql_chemistry::PolymorphicFilter publishingFeatures()
{
    return (table::Feature::isPublished.is(true) ||
            table::Feature::shouldBePublished.is(true));
}

} // namespace maps::mrc::db
