#pragma once

#include <maps/wikimap/mapspro/services/tasks-ng/lib/common.h>
#include <maps/wikimap/mapspro/services/tasks-ng/lib/db_context.h>
#include <maps/wikimap/mapspro/services/tasks-ng/lib/request_parameters.h>
#include <maps/wikimap/mapspro/services/tasks-ng/lib/xml_writer.h>

#include <pqxx/pqxx>
#include <memory>

namespace maps {
namespace wiki {
namespace tasks_ng {

class Task;

class BaseTask
{
public:
    explicit BaseTask(const pqxx::row& row);

    static std::unique_ptr<BaseTask> create(
        DbContext& ctx,
        const std::string& type,
        Uid uid,
        Id parentId);

    const Id id;
    const std::string type;
    const std::string created;
    const Uid createdBy;
    const Id parentId;
    const GrinderTaskId grinderTaskId;
    const bool frozen;

    static const std::string& fields();

    template <typename Data>
    std::unique_ptr<Data> loadDataFromRow(
        pqxx::transaction_base& txn,
        const std::string& table,
        const std::string& fields) const
    {
        auto res = resultFromRow(txn, table, fields, id);
        if (res.empty()) {
            return {};
        }
        return std::make_unique<Data>(res[0]);
    }

    static pqxx::result resultFromRow(
        pqxx::transaction_base& txn,
        const std::string& table,
        const std::string& fields,
        Id taskId);

    void writeProperties(XmlWriter& writer) const;
};


enum TaskWriteMode { Brief, Full };

class TaskContext
{
public:
    TaskContext() = default;
    virtual ~TaskContext() = default;

    virtual bool revokable() const { return false; }

    virtual void write(XmlWriter&, TaskWriteMode) const = 0;
};


class TaskResult
{
public:
    TaskResult() = default;
    virtual ~TaskResult() = default;

    virtual void write(XmlWriter&, TaskWriteMode) const = 0;
};

struct TaskData
{
    TaskData() : status(ServiceTaskStatus::Pending) {}

    ServiceTaskStatus status;
    std::string error;

    std::unique_ptr<BaseTask> baseTask;
    std::unique_ptr<TaskContext> context;
    std::unique_ptr<TaskResult> result;
};


class Task
{
public:
    explicit Task(const pqxx::row& row);

    Task(
        DbContext& ctx,
        const std::string& type,
        Uid uid,
        Id parentId,
        const RequestParameters& parameters);

    void launch(DbContext& ctx, Uid uid);
    void revoke(DbContext& ctx, Uid uid);
    void resume(DbContext& ctx, Uid uid);

    ServiceTaskStatus status() const { return data_.status; }

    bool revokable() const;
    bool resumable() const;

    void load(DbContext& ctx);
    void write(XmlWriter& writer, TaskWriteMode mode) const;

    auto id() const { return baseTask().id; }
    const auto& type() const { return baseTask().type; }
    auto createdBy() const { return baseTask().createdBy; }

    const TaskData& data() const { return data_; }
    const BaseTask& baseTask() const;

    template<typename Context>
    const Context& context() const
    {
        REQUIRE(data_.context, "Task: " << id() << " without context");
        auto ptr = dynamic_cast<const Context*>(data_.context.get());
        REQUIRE(ptr, "Wrong context, task: " << id());
        return *ptr;
    }

    template <typename Context>
    void setContext(std::unique_ptr<Context> ctx)
    {
        data_.context = std::move(ctx);
    }

    template <typename Context>
    void loadContext(
        DbContext& ctx, const std::string& table, const std::string& fields)
    {
        setContext(
            baseTask().loadDataFromRow<Context>(ctx.txnCore(), table, fields));
    }

    template <typename Result, typename ...Args>
    void loadResult(Args&& ...args)
    {
        data_.result = std::make_unique<Result>(std::forward<Args>(args)...);
    }

private:
    void updateStatus();
    void writeProperties(XmlWriter& writer) const;

    TaskData data_;
};

} // namespace tasks_ng
} // namespace wiki
} // namespace maps
