#include "options.h"

#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/db/include/enums.h>
#include <maps/libs/enum_io/include/enum_io.h>
#include <yandex/maps/wiki/common/string_utils.h>

#include <iostream>

namespace maps::fw_updater::storage::cli {

using namespace std::string_view_literals;

namespace {

const std::string BRANCH = "branch";
const std::string DEVICE_ID = "device-id";
const std::string FILE_PATH = "file-path";
const std::string HARDWARE = "hardware";
const std::string PERCENT = "percent";
const std::string RESULTS = "results";
const std::string ROLLOUT_ID = "rollout-id";
const std::string SKIP = "skip";
const std::string SLOT = "slot";
const std::string UPLOAD_ID = "upload-id";
const std::string VERSION = "version";

template <typename Enum>
std::string enumValues()
{
    return maps::wiki::common::join(enum_io::enumerateValues<Enum>(), '|');
}

} // namespace


constexpr enum_io::Representations<Command> COMMAND_STRINGS {
    {Command::Firmware, "firmware"sv},
    {Command::FirmwareUpload, "firmware-upload"sv},
    {Command::Rollout, "rollout"sv},
    {Command::DeviceBranch, "device-branch"sv}
};
DEFINE_ENUM_IO(Command, COMMAND_STRINGS);

constexpr enum_io::Representations<FirmwareSubCommand> FIRMWARE_SUBCOMMAND_STRINGS {
    {FirmwareSubCommand::Upload, "upload"sv},
    {FirmwareSubCommand::Delete, "delete"sv},
    {FirmwareSubCommand::List, "list"sv}
};
DEFINE_ENUM_IO(FirmwareSubCommand, FIRMWARE_SUBCOMMAND_STRINGS);

constexpr enum_io::Representations<FirmwareUploadSubCommand> FIRMWARE_UPLOAD_SUBCOMMAND_STRINGS {
    {FirmwareUploadSubCommand::List, "list"sv},
    {FirmwareUploadSubCommand::Delete, "delete"sv}
};
DEFINE_ENUM_IO(FirmwareUploadSubCommand, FIRMWARE_UPLOAD_SUBCOMMAND_STRINGS);

constexpr enum_io::Representations<RolloutSubCommand> ROLLOUT_SUBCOMMAND_STRINGS {
    {RolloutSubCommand::Create, "create"sv},
    {RolloutSubCommand::Expand, "expand"sv},
    {RolloutSubCommand::Pause, "pause"sv},
    {RolloutSubCommand::Resume, "resume"sv},
    {RolloutSubCommand::Target, "target"sv},
    {RolloutSubCommand::History, "history"sv},
};
DEFINE_ENUM_IO(RolloutSubCommand, ROLLOUT_SUBCOMMAND_STRINGS);

constexpr enum_io::Representations<DeviceBranchSubCommand> DEVICE_BRANCH_SUBCOMMAND_STRINGS {
    {DeviceBranchSubCommand::Add, "add"sv},
    {DeviceBranchSubCommand::Remove, "remove"sv},
    {DeviceBranchSubCommand::List, "list"sv},
};
DEFINE_ENUM_IO(DeviceBranchSubCommand, DEVICE_BRANCH_SUBCOMMAND_STRINGS);


Options::Options(maps::cmdline::Parser& parser)
    : parser(parser)
    , branch{BRANCH, parser.string(BRANCH)
        .help("branch (e.g. 'stable' or 'testing')")}
    , deviceId{DEVICE_ID, parser.string(DEVICE_ID)
        .help("device ID")}
    , filePath{FILE_PATH, parser.string(FILE_PATH)
        .help("path to the file to be uploaded")}
    , hardware{HARDWARE, parser.string(HARDWARE)
        .help("hardware name")}
    , percent{PERCENT, parser.option<uint16_t>(PERCENT).defaultValue(100)
        .help("rollout percentage, by default 100%")}
    , rolloutId{ROLLOUT_ID, parser.string(ROLLOUT_ID)
        .help("rollout ID")}
    , slot{SLOT, parser.string(SLOT)
        .help("slot for which the firmware is dedicated (e.g. 'rootfs' or 'appfs')")}
    , uploadId{UPLOAD_ID, parser.string(UPLOAD_ID)
        .help("upload ID")}
    , version{VERSION, parser.string(VERSION)
        .help("firmware version")}
    , results{RESULTS, parser.option<uint32_t>(RESULTS)
        .help("limit number of entries in the output")}
    , skip{SKIP, parser.option<uint32_t>(SKIP)
        .help("skip number of first entries in the output")}
{
}

void Options::validate() const
{
    if (parser.argv().size() < 2) {
        ERROR() << "Both COMMAND and SUBCOMMAND must be specified";
        parser.printUsageAndExit();
    }

    switch (command()) {
        case Command::Firmware:
            switch (subCommand<FirmwareSubCommand>()) {
                case FirmwareSubCommand::Upload:
                    requireOptions(hardware, slot, version, filePath);
                    break;
                case FirmwareSubCommand::Delete:
                    requireOptions(hardware, slot, version);
                    break;
                case FirmwareSubCommand::List:
                    requireOptions(hardware);
                    break;
            }
            break;

        case Command::FirmwareUpload:
            switch (subCommand<FirmwareUploadSubCommand>()) {
                case FirmwareUploadSubCommand::List:
                    break;
                case FirmwareUploadSubCommand::Delete:
                    requireOptions(uploadId);
                    break;
            }
            break;

        case Command::Rollout:
            switch (subCommand<RolloutSubCommand>()) {
                case RolloutSubCommand::Create:
                    requireOptions(hardware, slot, branch, version);
                    break;
                case RolloutSubCommand::Expand:
                    requireOptions(rolloutId, percent);
                    break;
                case RolloutSubCommand::Pause:
                case RolloutSubCommand::Resume:
                    requireOptions(rolloutId);
                    break;
                case RolloutSubCommand::Target:
                case RolloutSubCommand::History:
                    requireOptions(hardware);
                    break;
            }
            break;

        case Command::DeviceBranch:
            switch (subCommand<DeviceBranchSubCommand>()) {
                case DeviceBranchSubCommand::Add:
                    requireOptions(hardware, deviceId, branch);
                    break;
                case DeviceBranchSubCommand::Remove:
                    requireOptions(hardware, deviceId);
                    break;
                case DeviceBranchSubCommand::List:
                    requireOptions(hardware);
                    break;
            }
            break;
    }

    // Check that enum options convert to their types
    if (slot.option.defined()) {
        enum_io::fromString<db::Slot>(slot());
    }
}


std::string Options::helpText(const char* progName)
{
    std::ostringstream stream;
    stream << progName << " COMMAND SUBCOMMAND [OPTIONS...]\n\n";
    stream << "CLI tool to manage firmwares and rollouts.\n\n";
    stream << "COMMAND is one of {" << enumValues<Command>() << "}\n\n";
    stream << "SUBCOMMAND takes values depending on the COMMAND:\n";
    stream << "    COMMAND=" << Command::Firmware << ": {" << enumValues<FirmwareSubCommand>() << "}\n";
    stream << "    COMMAND=" << Command::FirmwareUpload << ": {" << enumValues<FirmwareUploadSubCommand>() << "}\n";
    stream << "    COMMAND=" << Command::Rollout << ": {" << enumValues<RolloutSubCommand>() << "}\n";
    stream << "    COMMAND=" << Command::DeviceBranch << ": {" << enumValues<DeviceBranchSubCommand>() << "}\n\n";
    stream << "OPTIONS are described below.";

    return stream.str();
}

} // namespace maps::fw_updater::storage::cli
