#pragma once

#include <maps/wikimap/mapspro/services/mrc/libs/common/include/exif.h>
#include <maps/wikimap/mapspro/services/mrc/libs/common/include/image_box.h>

#include <maps/wikimap/mapspro/services/mrc/libs/db/include/txn_id.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/include/eye/recognition_value.h>

#include <maps/libs/enum_io/include/enum_io_fwd.h>
#include <maps/libs/sql_chemistry/include/gateway_access.h>

#include <maps/libs/json/include/value.h>

#include <optional>
#include <string>
#include <utility>
#include <vector>


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

enum class RecognitionSource { Import, Model, Toloka, Yang };

DECLARE_ENUM_IO(RecognitionSource);


class Recognition {
public:

    Recognition(
            TId frameId,
            const common::ImageOrientation& orientation,
            RecognitionType type,
            RecognitionSource source,
            int16_t version);

    template<class Attrs>
    Recognition(
            TId frameId,
            const common::ImageOrientation& orientation,
            RecognitionType type,
            RecognitionSource source,
            int16_t version,
            const Attrs& attrs)
        : Recognition(frameId, orientation, type, source, version)
    {
        ASSERT(Attrs::RECOGNITION_TYPE == type);
        value_ = toJson(attrs);
    }

    TId id() const { return id_; }

    TId frameId() const { return frameId_; }

    TId txnId() const { return txnId_; }

    common::ImageOrientation orientation() const
    {
        return common::ImageOrientation::fromExif(orientation_);
    }

    RecognitionType type() const { return type_; }

    RecognitionSource source() const { return source_; }

    int16_t version() const { return version_; }

    auto orderKey() const { return std::make_tuple(source_, version_); }

    bool hasValue() const { return value_.has_value(); }

    template<class Attrs>
    Attrs value() const
    {
        ASSERT(value_);
        ASSERT(Attrs::RECOGNITION_TYPE == type_);
        return Attrs::fromJson(*value_);
    }

    Recognition& setVersion(int16_t version)
    {
        version_ = version;
        return *this;
    }

    template<class Attrs>
    Recognition& setValue(const Attrs& attrs)
    {
        ASSERT(Attrs::RECOGNITION_TYPE == type_);
        value_ = toJson(attrs);
        return *this;
    }

    bool empty() const { return value_.has_value() && value_.value().empty(); }

private:
    friend class sql_chemistry::GatewayAccess<Recognition>;
    friend class TxnIdAccess<Recognition>;

    Recognition() {}

    Recognition& setTxnId(TId txnId) {
        txnId_ = txnId;
        return *this;
    }

    template <typename T>
    static auto introspect(T& t)
    {
        return std::tie(t.id_, t.txnId_, t.frameId_, t.orientation_, t.type_, t.source_, t.version_, t.value_);
    }

    TId id_;
    TId txnId_;
    TId frameId_;
    int16_t orientation_;
    RecognitionType type_;
    RecognitionSource source_;
    int16_t version_;
    std::optional<json::Value> value_;

public:
    auto introspect() const { return introspect(*this); }
};

using Recognitions = std::vector<Recognition>;

std::optional<DetectionType> toDetectionType(RecognitionType type);

RecognitionType toRecognitionType(DetectionType type);

class DetectionGroup {

public:
    DetectionGroup(TId frameId, DetectionType type)
        : id_(0)
        , txnId_(0)
        , frameId_(frameId)
        , type_(type)
        , approved_(false)
    {}

    TId id() const { return id_; }

    TId frameId() const { return frameId_; }

    TId txnId() const { return txnId_; }

    DetectionType type() const { return type_; }

    bool approved() const { return approved_; }

    DetectionGroup& setApproved(bool flag)
    {
        approved_ = flag;
        return *this;
    }

private:
    friend class sql_chemistry::GatewayAccess<DetectionGroup>;
    friend class TxnIdAccess<DetectionGroup>;

    DetectionGroup() {}

    DetectionGroup& setTxnId(TId txnId) {
        txnId_ = txnId;
        return *this;
    }

    template <typename T>
    static auto introspect(T& t)
    {
        return std::tie(t.id_, t.txnId_, t.frameId_, t.type_, t.approved_);
    }

    TId id_;
    TId txnId_;
    TId frameId_;
    DetectionType type_;
    bool approved_;

public:
    auto introspect() const { return introspect(*this); }
};

using DetectionGroups = std::vector<DetectionGroup>;

class Detection {
public:
    template<class T>
    Detection(
            TId groupId,
            const T& attrs)
        : id_(0)
        , txnId_(0)
        , groupId_(groupId)
        , deleted_(false)
        , attrs_(toJson(attrs))
    {}

    TId id() const { return id_; }

    TId groupId() const { return groupId_; }

    TId txnId() const { return txnId_; }

    bool deleted() const { return deleted_; }

    template<class T>
    T attrs() const { return T::fromJson(attrs_); }

    common::ImageBox box() const { return BaseDetectedObject::fromJson(attrs_).box; }

    Detection& setDeleted(bool deleted = true)
    {
        deleted_ = deleted;
        return *this;
    }

private:
    friend class sql_chemistry::GatewayAccess<Detection>;
    friend class TxnIdAccess<Detection>;

    Detection() {}

    Detection& setTxnId(TId txnId) {
        txnId_ = txnId;
        return *this;
    }

    template <typename T>
    static auto introspect(T& t)
    {
        return std::tie(t.id_, t.txnId_, t.groupId_, t.deleted_, t.attrs_);
    }

    TId id_;
    TId txnId_;
    TId groupId_;
    bool deleted_;
    json::Value attrs_{json::null};

public:
    auto introspect() const { return introspect(*this); }
};

using Detections = std::vector<Detection>;

class RecognitionTask {
public:
    RecognitionTask(
        TId recognitionId,
        TId taskId,
        std::optional<TId> parentId = std::nullopt)
        : id_(0)
        , parentId_(std::move(parentId))
        , recognitionId_(recognitionId)
        , taskId_(taskId)
    {}

    TId id() const { return id_; }

    std::optional<TId> parentId() const { return parentId_; }

    TId recognitionId() const { return recognitionId_; }

    TId taskId() const { return taskId_; }

    TId txnId() const { return txnId_; }

private:
    friend class sql_chemistry::GatewayAccess<RecognitionTask>;
    friend class TxnIdAccess<RecognitionTask>;

    RecognitionTask() {}

    RecognitionTask& setTxnId(TId txnId) {
        txnId_ = txnId;
        return *this;
    }

    template <typename T>
    static auto introspect(T& t) {
        return std::tie(t.id_, t.parentId_, t.recognitionId_, t.taskId_, t.txnId_);
    }

    TId id_;
    std::optional<TId> parentId_;
    TId recognitionId_;
    TId taskId_;
    TId txnId_;

public:
    auto introspect() const { return introspect(*this); }
};

using RecognitionTasks = std::vector<RecognitionTask>;

class DetectionRelation {
public:
    DetectionRelation(
        TId masterDetectionId,
        TId slaveDetectionId)
        : id_(0)
        , masterDetectionId_(masterDetectionId)
        , slaveDetectionId_(slaveDetectionId)
        , txnId_(0)
        , deleted_(false)
    {}

    TId id() const { return id_; }

    TId masterDetectionId() const { return masterDetectionId_; }

    TId slaveDetectionId() const { return slaveDetectionId_; }

    TId txnId() const { return txnId_; }

    bool deleted() const { return deleted_; }

    DetectionRelation& setDeleted(bool flag) {
        deleted_ = flag;
        return *this;
    }
private:
    friend class sql_chemistry::GatewayAccess<DetectionRelation>;
    friend class TxnIdAccess<DetectionRelation>;

    DetectionRelation() {}

    DetectionRelation& setTxnId(TId txnId) {
        txnId_ = txnId;
        return *this;
    }

    template <typename T>
    static auto introspect(T& t) {
        return std::tie(t.id_, t.masterDetectionId_, t.slaveDetectionId_, t.txnId_, t.deleted_);
    }

    TId id_;
    TId masterDetectionId_;
    TId slaveDetectionId_;
    TId txnId_;
    bool deleted_;

public:
    auto introspect() const { return introspect(*this); }
};

using DetectionRelations = std::vector<DetectionRelation>;

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