#pragma once

#include "task.h"

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

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

struct TaskName : Table<db::ugc::TaskName> {
    static constexpr std::string_view name_{"ugc.task_name"sv};

    // PRIMARY KEY FOREIGN KEY -> ugc.task
    static constexpr Int64PrimaryKey taskId{"task_id"sv, name_};
    // PRIMARY KEY
    static constexpr StringPrimaryKey locale{"locale"sv, name_};
    static constexpr StringColumn value{"value"sv, name_};

    static constexpr auto columns_() {
        return std::tie(taskId, locale, value);
    }
};

struct Target : Table<db::ugc::Target> {
    static constexpr std::string_view name_{"ugc.target"sv};

    static constexpr BigSerialKey id{"target_id"sv, name_};
    // FOREIGN KEY -> ugc.task
    static constexpr NumericColumn<TId> taskId{"task_id"sv, name_};
    static constexpr BooleanColumn oneway{"oneway"sv, name_};
    static constexpr EnumColumn<Traffic> traffic{"traffic"sv, name_};
    static constexpr EnumColumn<Direction> direction{"direction"sv, name_};
    static constexpr GeodeticColumn<geolib3::Polyline2> geom{"geom"sv, name_};
    static constexpr NullableNumericColumn<uint32_t> forwardPos{"forward_pos"sv, name_};
    static constexpr NullableNumericColumn<uint32_t> backwardPos{"backward_pos"sv, name_};

    static constexpr auto columns_() {
        return std::tie(id, taskId, oneway, traffic, direction, geom, forwardPos, backwardPos);
    }
};

struct Task : Table<db::ugc::Task> {
    static constexpr std::string_view name_{"ugc.task"sv};

    static constexpr BigSerialKey id{"task_id"sv, name_};
    static constexpr EnumColumn<TaskStatus> status{"status"sv, name_};
    static constexpr GeodeticColumn<geolib3::MultiPolygon2> hull{"hull"sv, name_};
    static constexpr NumericColumn<int64_t> duration{"duration"sv, name_};
    static constexpr DoubleColumn distanceInMeters{"distance"sv, name_};
    // FOREIGN KEY -> ugc.tasks_group
    static constexpr NullableNumericColumn<TId> tasksGroupId{"tasks_group_id"sv, name_};

    static constexpr XminColumn xMin{name_};

    static constexpr Nullable<NumericArrayColumn<int16_t>> cameraDeviations{
        "camera_deviations"sv, name_};

    static constexpr auto columns_() {
        return std::tie(id, status, hull, duration, distanceInMeters, tasksGroupId, xMin,
            cameraDeviations);
    }
};

} // namespace table

using TaskNameGateway = sql_chemistry::Gateway<table::TaskName>;
using TargetGateway = sql_chemistry::Gateway<table::Target>;

class TaskGateway : public sql_chemistry::Gateway<table::Task> {
public:
    using Tb = table::Task;
    using Base = Gateway<Tb>;
    using Base::Gateway;
    using Base::Entity;
    using Base::Entities;
    using Base::EntitiesRef;

    void loadNames(Entity& task) {
        loadNames(TArrayRef(&task, 1));
    }

    void loadNames(EntitiesRef tasks) {
        auto names = TaskNameGateway{txn()}.load(table::TaskName::taskId.in(ids(tasks)));
        std::unordered_map<TId, ugc::TaskNamesMap> taskNamesMaps;
        for (auto& name: names) {
            taskNamesMaps[name.taskId()][name.locale()] = name.value();
        }
        for (auto& task: tasks) {
            task.loadNames(std::move(taskNamesMaps[task.id()]));
        }
    };

    void loadTargets(Entity& task) {
        auto targets = TargetGateway{txn()}.load(table::Target::taskId == id(task));
        task.loadTargets(std::move(targets));
    }

    void loadTargets(EntitiesRef tasks) {
        auto targets = TargetGateway{txn()}.load(table::Target::taskId.in(ids(tasks)));
        std::unordered_map<TId, ugc::Targets> targetsMap;
        for (auto& target: targets) {
            targetsMap[target.taskId()].push_back(std::move(target));
        }
        for (auto& task: tasks) {
            task.loadTargets(std::move(targetsMap[task.id()]));
        }
    }

    void loadAlso(Entity& task, ugc::LoadNames names, ugc::LoadTargets targets) {
        if (names == LoadNames::Yes) {
            loadNames(task);
        }
        if (targets == LoadTargets::Yes) {
            loadTargets(task);
        }
    }

    void loadAlso(EntitiesRef tasks, ugc::LoadNames names, ugc::LoadTargets targets) {
        if (names == LoadNames::Yes) { loadNames(tasks); }
        if (targets == LoadTargets::Yes) { loadTargets(tasks); }
    }

    void upsert(EntitiesRef) = delete;

    void insert(Entity& task) {
        Base::insert(task);
        TargetGateway{txn()}.insert(task.targetsToUpdate());
        TaskNameGateway{txn()}.insert(task.namesToUpdate());
    }
    void insert(EntitiesRef tasks) {
        Base::insert(tasks);
        for (auto& task: tasks) { // Assume that tasks inserted mostly one by one.
            TargetGateway{txn()}.insert(task.targetsToUpdate());
            TaskNameGateway{txn()}.insert(task.namesToUpdate());
        }
    }

    bool tryUpdate(Entity&) = delete;

    void update(Entity& task) {
        Base::update(task);
        task.checkCanUpdate();
        TargetGateway{txn()}.update(task.targetsToUpdate());
        TaskNameGateway{txn()}.update(task.namesToUpdate());
    }

    void update(EntitiesRef tasks) {
        Base::update(tasks);
        for (auto& task: tasks) { // Assume that tasks updated mostly one by one.
            task.checkCanUpdate();
            TargetGateway{txn()}.update(task.targetsToUpdate());
            TaskNameGateway{txn()}.update(task.namesToUpdate());
        }
    }

private:
    static std::vector<TId> ids(EntitiesRef es) {
        std::vector<TId> result(es.size());
        for (size_t i = 0; i < es.size(); ++i) { result[i] = es[i].id(); }
        return result;
    }
};

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