#pragma once

#include <maps/wikimap/mapspro/services/mrc/libs/db/include/common.h>
#include <maps/wikimap/mapspro/services/mrc/libs/db/impl/utility.h>

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

#include <maps/libs/common/include/exception.h>
#include <maps/libs/geolib/include/multipolygon.h>
#include <maps/libs/chrono/include/time_point.h>
#include <maps/libs/enum_io/include/enum_io_fwd.h>

#include <optional>

namespace maps::mrc::db::ugc {

enum class TasksGroupStatus {
    Draft,
    Generating,
    Open,
    InProgress,
    Closed,
    Failed
};

DECLARE_ENUM_IO(TasksGroupStatus);

enum class UseRouting : bool {
    No = false,
    Yes = true
};

enum class UseToll : bool {
    No = false,
    Yes = true
};

class TasksGroup {
public:
    TasksGroup(
        TasksGroupStatus status,
        std::string name,
        geolib3::MultiPolygon2 mercatorGeom,
        UseRouting useRouting,
        std::vector<int> fcs,
        UseToll useToll,
        uint32_t recommendedTaskLength)
        : status_(status)
        , name_(std::move(name))
        , mercatorGeom_(std::move(mercatorGeom))
        , useRouting_(static_cast<bool>(useRouting))
        , fcs_(std::move(fcs))
        , useToll_(static_cast<bool>(useToll))
        , recommendedTaskLength_(recommendedTaskLength) {
        ASSERT(!fcs_.empty());
    }

    TId id() const { return id_; }

    TasksGroupStatus status() const { return status_; }

    const std::string& name() const { return name_; }

    const geolib3::MultiPolygon2& mercatorGeom() const { return mercatorGeom_; }

    UseRouting useRouting() const { return static_cast<UseRouting>(useRouting_); }

    const std::vector<int>& fcs() const { return fcs_; }

    UseToll useToll() const { return static_cast<UseToll>(useToll_); }

    uint32_t recommendedTaskLengthMeters() const { return recommendedTaskLength_; }

    std::optional<float> minEdgeCoverageRatio() const { return minEdgeCoverageRatio_; }

    bool excludeDeadends() const { return excludeDeadends_; }

    bool ignorePrivateArea() const { return ignorePrivateArea_; }

    std::optional<chrono::TimePoint> actualizedBefore() const { return actualizedBefore_; }

    std::optional<uint32_t> totalLengthMeters() const { return totalLengthMeters_; }

    std::optional<uint32_t> uniqueLengthMeters() const { return uniqueLengthMeters_; }

    std::optional<float> graphCoverageRatio() const { return graphCoverageRatio_; }

    std::vector<CameraDeviation> cameraDeviations() const
    {
        if (cameraDeviations_.has_value()) {
            return castItemsTo<CameraDeviation>(cameraDeviations_.value());
        }
        else {
            return {CameraDeviation::Front};
        }
    }

    bool hasCreatedBy() const { return createdBy_.has_value(); }
    const std::string& createdBy() const {
        ASSERT(createdBy_);
        return createdBy_.value();
    }

    const std::optional<std::vector<std::string>>& allowedAssigneesLogins() const
    {
        return allowedAssigneesLogins_;
    }

    TasksGroup& setStatus(TasksGroupStatus status) {
        status_ = status;
        return *this;
    }

    TasksGroup& setName(const std::string& name) {
        name_ = name;
        return *this;
    }

    TasksGroup& setMercatorGeom(geolib3::MultiPolygon2 geom) {
        mercatorGeom_ = std::move(geom);
        return *this;
    }

    TasksGroup& setUseRouting(UseRouting useRouting) {
        useRouting_ = static_cast<std::underlying_type<UseRouting>::type>(useRouting);
        return *this;
    }

    TasksGroup& setFcs(std::vector<int> fcs) {
        fcs_ = std::move(fcs);
        return *this;
    }

    TasksGroup& setUseToll(UseToll useToll) {
        useToll_ = static_cast<std::underlying_type<UseToll>::type>(useToll);
        return *this;
    }

    TasksGroup& setRecommendedTaskLengthMeters(uint32_t recommendedTaskLength) {
        recommendedTaskLength_ = recommendedTaskLength;
        return *this;
    }

    TasksGroup& setMinEdgeCoverageRatio(float minEdgeCoverageRatio) {
        REQUIRE(0. <= minEdgeCoverageRatio && minEdgeCoverageRatio <= 1., "invalid minEdgeCoverageRatio value " << minEdgeCoverageRatio);
        minEdgeCoverageRatio_ = minEdgeCoverageRatio;
        return *this;
    }

    TasksGroup& setExcludeDeadends(bool excludeDeadends) {
        excludeDeadends_ = excludeDeadends;
        return *this;
    }

    TasksGroup& setIgnorePrivateArea(bool ignorePrivateArea) {
        ignorePrivateArea_ = ignorePrivateArea;
        return *this;
    }

    TasksGroup& setActualizedBefore(chrono::TimePoint tp) {
        actualizedBefore_ = tp;
        return *this;
    }

    TasksGroup& setTotalLengthMeters(uint32_t totalLengthMeters) {
        totalLengthMeters_ = totalLengthMeters;
        return *this;
    }

    TasksGroup& setUniqueLengthMeters(uint32_t uniqueLengthMeters) {
        uniqueLengthMeters_ = uniqueLengthMeters;
        return *this;
    }

    TasksGroup& setGraphCoverageRatio(float ratio) {
        graphCoverageRatio_ = ratio;
        return *this;
    }

    TasksGroup&
    setCameraDeviations(const std::vector<CameraDeviation>& cameraDeviations)
    {
        cameraDeviations_ = castItemsTo<int16_t>(cameraDeviations);
        return *this;
    }

    TasksGroup& setCreatedBy(std::string value)
    {
        createdBy_ = std::move(value);
        return *this;
    }

    TasksGroup& setAllowedAssigneesLogins(std::optional<std::vector<std::string>> allowedAssigneesLogins)
    {
        allowedAssigneesLogins_ = std::move(allowedAssigneesLogins);
        return *this;
    }

private:
    friend class sql_chemistry::GatewayAccess<TasksGroup>;
    TasksGroup() = default;

    template <typename T>
    static auto introspect(T& t) {
        return std::tie(t.id_, t.status_, t.name_, t.mercatorGeom_,
            t.useRouting_, t.fcs_, t.useToll_, t.recommendedTaskLength_,
            t.minEdgeCoverageRatio_, t.excludeDeadends_, t.ignorePrivateArea_,
            t.actualizedBefore_, t.totalLengthMeters_,
            t.uniqueLengthMeters_, t.graphCoverageRatio_, t.cameraDeviations_,
            t.createdBy_, t.allowedAssigneesLogins_, t.xMin_);
    }

    TId id_{0u};
    TasksGroupStatus status_;
    std::string name_;
    geolib3::MultiPolygon2 mercatorGeom_;
    bool useRouting_;
    std::vector<int> fcs_;
    bool useToll_;
    uint32_t recommendedTaskLength_;
    std::optional<float> minEdgeCoverageRatio_;
    bool excludeDeadends_{false};
    bool ignorePrivateArea_{false};
    std::optional<chrono::TimePoint> actualizedBefore_;
    std::optional<uint32_t> totalLengthMeters_;
    std::optional<uint32_t> uniqueLengthMeters_;
    std::optional<float> graphCoverageRatio_;
    std::optional<std::vector<int16_t>> cameraDeviations_;
    std::optional<UserId> createdBy_;
    std::optional<std::vector<std::string>> allowedAssigneesLogins_;

    uint64_t xMin_{0};

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

class TasksGroupEmail {
public:
    TasksGroupEmail(TId tasksGroupId, std::string email)
        : tasksGroupId_(tasksGroupId), email_(std::move(email))
    {
    }

    TId tasksGroupId() const { return tasksGroupId_; }

    const std::string& email() const { return email_; }

private:
    friend class sql_chemistry::GatewayAccess<TasksGroupEmail>;

    TasksGroupEmail() = default;

    template <typename T>
    static auto introspect(T& t)
    {
        return std::tie(t.tasksGroupId_, t.email_);
    }

    TId tasksGroupId_;
    std::string email_;

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

using TasksGroupEmails = std::vector<TasksGroupEmail>;

} // namespace maps::mrc::db::ugc
