#include "grade.h"

#include <maps/wikimap/mapspro/libs/assessment/include/unit.h>
#include <maps/wikimap/mapspro/libs/assessment/impl/sql_helpers.h>
#include <maps/wikimap/mapspro/libs/assessment/impl/magic_strings.h>

#include <fmt/format.h>

namespace maps::wiki::assessment::units {

using namespace fmt::literals;

namespace {

const std::string UPDATE_BASIC_COUNT_FMT = fmt::format(
    "UPDATE {unit} "
    "SET basic_{{value}} = basic_{{value}} + 1, {last_graded_at} = NOW() "
    "WHERE {unit_id} = {{unit_id}} ",

    "unit"_a = sql::table::UNIT,
    "unit_id"_a = sql::col::UNIT_ID,
    "last_graded_at"_a = sql::col::LAST_GRADED_AT);

const std::string UPDATE_LAST_EXPERT_VALUE_FMT = fmt::format(
    "UPDATE {unit} "
    "SET {last_expert_value} = '{{value}}', {last_graded_at} = NOW()"
    "WHERE {unit_id} = {{unit_id}}",

    "unit"_a = sql::table::UNIT,
    "unit_id"_a = sql::col::UNIT_ID,
    "last_expert_value"_a = sql::col::LAST_EXPERT_VALUE,
    "last_graded_at"_a = sql::col::LAST_GRADED_AT);

Grade makeGrade(const pqxx::row& row)
{
    return {
        row[sql::col::GRADE_ID].as<TId>(),
        row[sql::col::GRADED_BY].as<TUid>(),
        chrono::parseSqlDateTime(row[sql::col::GRADED_AT].c_str()),
        enum_io::fromString<Grade::Value>(row[sql::col::VALUE].c_str()),
        row[sql::col::COMMENT].is_null()
            ? std::nullopt
            : std::make_optional(row[sql::col::COMMENT].c_str()),
        enum_io::fromString<Qualification>(row[sql::col::QUALIFICATION].c_str())
   };
}

} // namespace

Grade addGrade(
    pqxx::transaction_base& txn,
    TUid gradedBy,
    TId unitId,
    Grade::Value value,
    const std::optional<std::string>& comment,
    Qualification qualification)
{
    const auto result = txn.exec(
        "INSERT INTO " + sql::table::GRADE + " "
        "(" +
            sql::col::UNIT_ID + ", " +
            sql::col::GRADED_BY + ", " +
            sql::col::VALUE + ", " +
            sql::col::COMMENT + ", " +
            sql::col::QUALIFICATION +
        ") "
        "VALUES "
        "(" +
            std::to_string(unitId) + ", " +
            std::to_string(gradedBy) + ", " +
            sqlEnumToString(txn, value) + ", " +
            (comment ? txn.quote(*comment) : "NULL") + ", " +
            sqlEnumToString(txn, qualification) + 
        ")"
        "ON CONFLICT "
        "(" +
            sql::col::UNIT_ID + ", " +
            sql::col::GRADED_BY + ", " +
            sql::col::GRADED_AT +
        ") "
        "DO "
            "NOTHING "
        "RETURNING *");

    REQUIRE(result.size() == 1, "Inserted unexpected number of grades: " << result.size());
    return makeGrade(result[0]);
}

void updateGradeStats(
    pqxx::transaction_base& txn,
    TId unitId,
    Grade::Value value,
    Qualification qualification)
{
    const auto result = txn.exec(fmt::format(
        qualification == Qualification::Basic
        ? UPDATE_BASIC_COUNT_FMT
        : UPDATE_LAST_EXPERT_VALUE_FMT,

        "unit_id"_a = unitId,
        "value"_a = toString(value)));

    REQUIRE(
        result.affected_rows() == 1,
        "Failed to update grade stats for unit_id = " << unitId << ". "
        "Update affects " << result.affected_rows() << " units.");
}

} // namespace maps::wiki::assessment::units
