#include "stats.h"
#include "helpers.h"
#include <maps/wikimap/mapspro/libs/social/helpers.h>
#include <maps/wikimap/mapspro/libs/social/magic_strings.h>

#include <maps/libs/common/include/exception.h>

#include <fmt/format.h>

#include <sstream>

namespace maps::wiki::social::tasks {

using namespace fmt::literals;

namespace {

const std::string COUNT = "count";
const std::string COUNT_EXPR = COUNT + "(*)";
const std::string OLDEST = "oldest";

const std::string TASK_TABLE_ALIAS = "t.";

const std::string MINUS_DAY = "-'1 day'::interval";

const std::string GROUP_BY_AOI = " GROUP BY " + sql::col::AOI_ID;

std::string introDateExpression(
    ModerationMode mode,
    const ModerationTimeIntervals& moderationTimeIntervals)
{
    switch (mode) {
        case ModerationMode::Moderator:
            return sql::col::CREATED_AT;
        case ModerationMode::SuperModerator:
        case ModerationMode::Supervisor:
            return fmt::format(
                "COALESCE({resolved_at} + {resolve_delay}, {created_at} + {create_delay})",
                "resolved_at"_a = sql::col::RESOLVED_AT,
                "created_at"_a = sql::col::CREATED_AT,
                "resolve_delay"_a = sqlIntervalInSeconds(moderationTimeIntervals.supervisorDelay),
                "create_delay"_a = sqlIntervalInSeconds(moderationTimeIntervals.superModeratorDelay)
            );
    }
    throw maps::LogicError() << "Unexpected moderation mode";
}

std::string filterByEventTypeClause(
        const std::optional<EventType> eventType,
        const std::string& tableAlias)
{
    if (!eventType) {
        return {};
    }

    std::ostringstream oss;
    oss << " AND " << tableAlias << sql::col::TYPE << "='" << *eventType << "'";
    return oss.str();
}

std::string filterByCategory(
    pqxx::transaction_base& work,
    const std::optional<CategoryIdsSet>& categoryIds)
{
    return filterParamClause(
        categoryIds, TASK_TABLE_ALIAS, sql::col::PRIMARY_OBJECT_CATEGORY_ID, work);
}

} // namespace

std::map<TId, ActiveTaskStat> activeTaskStatsByAoi(
        pqxx::transaction_base& work,
        ModerationMode mode,
        const std::optional<CategoryIdsSet>& categoryIds,
        const std::optional<EventType> eventType,
        const ModerationTimeIntervals& moderationTimeIntervals)
{
    auto query =
        "SELECT " + sql::col::AOI_ID + ", " + COUNT_EXPR + ","
        " min(" + introDateExpression(mode, moderationTimeIntervals) + ") as " + OLDEST +
            " FROM "  + sql::table::TASK_ACTIVE + " t"
                " JOIN " + sql::table::AOI_FEED_TRUNK_TASK_ACTIVE +
                " USING (" + sql::col::EVENT_ID + ")"
            " WHERE TRUE " +
                commitEventTaskClause(mode, TASK_TABLE_ALIAS, moderationTimeIntervals) +
                filterByCategory(work, categoryIds) +
                filterByEventTypeClause(eventType, TASK_TABLE_ALIAS) +
            GROUP_BY_AOI;

    std::map<TId, ActiveTaskStat> taskStatsByAoi;
    for (const auto& row : work.exec(query)) {
        taskStatsByAoi.insert({
            row[sql::col::AOI_ID].as<TId>(),
            ActiveTaskStat{row[COUNT].as<size_t>(), row[OLDEST].as<std::string>()}
        });
    }

    return taskStatsByAoi;
}

std::map<TId, size_t> recentNewTaskCountsByAoi(
        pqxx::transaction_base& work,
        ModerationMode mode,
        const std::optional<CategoryIdsSet>& categoryIds,
        const std::optional<EventType> eventType,
        const ModerationTimeIntervals& moderationTimeIntervals)
{
    auto query =
        "SELECT " + sql::col::AOI_ID + ", " + COUNT_EXPR +
            " FROM "  + sql::table::TASK + " t"
                " JOIN " + sql::table::AOI_FEED_TRUNK_TASK + " USING (" + sql::col::EVENT_ID + ")"
            " WHERE ";

    switch (mode) {
        case ModerationMode::Moderator:
            query += sql::col::CREATED_AT + " > " + sql::value::NOW + MINUS_DAY;
            break;

        case ModerationMode::SuperModerator:
        case ModerationMode::Supervisor:
            query +=
                fmt::format(
                    "("
                    "  {created_at} < NOW() - {create_delay} AND"
                    "  {created_at} > NOW() - {create_delay} - '1 day'::interval"
                    "  OR"
                    "  {resolved_at} < NOW() - {resolve_delay} AND"
                    "  {resolved_at} > NOW() - {resolve_delay} - '1 day'::interval"
                    ")",
                    "created_at"_a = sql::col::CREATED_AT,
                    "resolved_at"_a = sql::col::RESOLVED_AT,
                    "resolved_by"_a = sql::col::RESOLVED_BY,
                    "create_delay"_a = sqlIntervalInSeconds(moderationTimeIntervals.superModeratorDelay),
                    "resolve_delay"_a = sqlIntervalInSeconds(moderationTimeIntervals.supervisorDelay)
                );
            break;
    }
    query += filterByCategory(work, categoryIds);
    query += filterByEventTypeClause(eventType, TASK_TABLE_ALIAS);
    query += GROUP_BY_AOI;

    std::map<TId, size_t> taskCountByAoi;
    for (const auto& row : work.exec(query)) {
        taskCountByAoi.insert({row[sql::col::AOI_ID].as<TId>(), row[COUNT].as<size_t>()});
    }

    return taskCountByAoi;
}

std::map<TId, size_t> recentProcessedTaskCountsByAoi(
        pqxx::transaction_base& work,
        ModerationMode mode,
        const std::optional<CategoryIdsSet>& categoryIds,
        const std::optional<EventType> eventType,
        const ModerationTimeIntervals& moderationTimeIntervals)
{
    auto query =
        "SELECT " + sql::col::AOI_ID + ", " + COUNT_EXPR +
            " FROM "  + sql::table::TASK + " t"
                " JOIN " + sql::table::AOI_FEED_TRUNK_TASK + " USING (" + sql::col::EVENT_ID + ")"
            " WHERE ";

    switch (mode) {
        case ModerationMode::Moderator:
            query += sql::col::RESOLVED_AT + " > " + sql::value::NOW + MINUS_DAY;
            break;

        case ModerationMode::SuperModerator:
        case ModerationMode::Supervisor:
            query += fmt::format(
                "("
                    "{created_at} < NOW() - {create_delay} AND {resolved_at} > NOW() - '1 day'::interval OR "
                    "{resolved_at} < NOW() - {resolve_delay} AND {closed_at} > NOW() - '1 day'::interval"
                ")",
                "created_at"_a = sql::col::CREATED_AT,
                "resolved_at"_a = sql::col::RESOLVED_AT,
                "closed_at"_a = sql::col::CLOSED_AT,
                "create_delay"_a = sqlIntervalInSeconds(moderationTimeIntervals.superModeratorDelay),
                "resolve_delay"_a = sqlIntervalInSeconds(moderationTimeIntervals.supervisorDelay)
            );
            break;
    }
    query += filterByCategory(work, categoryIds);
    query += filterByEventTypeClause(eventType, TASK_TABLE_ALIAS);
    query += GROUP_BY_AOI;

    std::map<TId, size_t> taskCountByAoi;
    for (const auto& row : work.exec(query)) {
        taskCountByAoi.insert({row[sql::col::AOI_ID].as<TId>(), row[COUNT].as<size_t>()});
    }

    return taskCountByAoi;
}

size_t todayProcessedTaskCountByUid(
    pqxx::transaction_base& work,
    TUid uid,
    int tzInMinutes)
{
    std::ostringstream query;
    query <<
        "SELECT " << COUNT_EXPR <<
        " FROM (" <<
            " SELECT " << sql::col::EVENT_ID <<
            " FROM " << sql::table::TASK_CLOSED <<
            " WHERE (" <<
                sql::col::RESOLVED_BY << " = " << uid <<
                " AND " <<
                sql::col::RESOLVED_AT << " >= " << userMidnightExpression(tzInMinutes) <<
            ") OR (" <<
                sql::col::CLOSED_BY << " = " << uid <<
                " AND " <<
                sql::col::CLOSED_AT << " >= " << userMidnightExpression(tzInMinutes) <<
            ")" <<
          " UNION ALL" <<
            " SELECT " << sql::col::EVENT_ID <<
            " FROM " << sql::table::TASK_ACTIVE <<
            " WHERE " <<
                sql::col::RESOLVED_BY << " = " << uid <<
                " AND "
                << sql::col::RESOLVED_AT << " >= " << userMidnightExpression(tzInMinutes) <<
          ") t" <<
        " JOIN " << sql::table::COMMIT_EVENT << " as ce USING (" << sql::col::EVENT_ID << ")" <<
        " WHERE ce." << sql::col::CREATED_BY << " != " << uid;
    auto rows = work.exec(query.str());
    return (*rows.begin())[COUNT].as<size_t>();
}

} // namespace maps::wiki::social::tasks
