#include <maps/wikimap/mapspro/libs/flat_range/include/validation.h>
#include <maps/wikimap/mapspro/libs/flat_range/include/range.h>
#include <maps/libs/enum_io/include/enum_io.h>

#include <unicode/uchar.h>
#include <unicode/unistr.h>
#include <yandex/maps/wiki/common/icu_helpers.h>

#include <unordered_set>

namespace maps::wiki::flat_range {

namespace {

const int MIN_FLAT = 1;
const int MAX_FLAT = 7500;

const int MIN_LEVEL = 0;
const int MAX_LEVEL = 500;

enum class OutOfRange
{
    No,
    Yes
};

enum class ReverseOrder
{
    No,
    Yes
};

struct LimitsValidationResult {
    OutOfRange outOfRange;
    ReverseOrder reverseOrder;
};

bool areValidCharsInNumber(const std::string& number)
{
    icu::UnicodeString unicodeString = icu::UnicodeString::fromUTF8(number);
    auto wstr = common::icuToUtf32(unicodeString);

    for (auto wch : wstr) {
        if (u_isalpha(wch) && !u_isupper(wch)) {
            return false;
        }
    }
    return true;
}

bool areValidCharsInRange(const Range& range)
{
    for (size_t i = 0; i < range.size(); ++i) {
        if (!areValidCharsInNumber(range.value(i))) {
            return false;
        }
    }
    return true;
}

LimitsValidationResult areValidLimitsInRange(
    const Range& range,
    int minValue,
    int maxValue)
{
    if (range.named()) {
        return {};
    }
    const auto firstValue = std::stoi(range.first());
    const auto lastValue = std::stoi(range.last());
    LimitsValidationResult result;
    result.outOfRange =
        (firstValue < minValue) || (firstValue > maxValue) ||
        (lastValue < minValue) || (lastValue > maxValue)
        ? OutOfRange::Yes
        : OutOfRange::No;
    result.reverseOrder =
        firstValue > lastValue
        ? ReverseOrder::Yes
        : ReverseOrder::No;
    return result;
}

} // namespace

constexpr enum_io::Representations<ValidationResult> VALIDATION_RESULT_ENUM_REPRESENTATION {
    {ValidationResult::BadFlatRange, "BadFlatRange"},
    {ValidationResult::BadLevelRange, "BadLevelRange"},
    {ValidationResult::FlatRangesIntersection, "FlatRangesIntersection"},
    {ValidationResult::FlatsOutOfRange, "FlatsOutOfRange"},
    {ValidationResult::LevelsOutOfRange, "LevelsOutOfRange"},
    {ValidationResult::TooManyFlatsPerEntrance, "TooManyFlatsPerEntrance"},
    {ValidationResult::FlatsReverseOrder, "FlatsReverseOrder"},
    {ValidationResult::LevelsReverseOrder, "LevelsReverseOrder"},
};

DEFINE_ENUM_IO(ValidationResult, VALIDATION_RESULT_ENUM_REPRESENTATION);

std::set<ValidationResult>
validate(const std::vector<FlatLevelRange>& flatLevelRanges)
{
    std::set<ValidationResult> result;

    std::unordered_set<std::string> flatNames;
    for (const auto& flatLevelRange : flatLevelRanges) {
        Ranges flatRanges;
        try {
            flatRanges = parse(flatLevelRange.flats);
        } catch (ParseException&) {
            result.insert(ValidationResult::BadFlatRange);
        }
        for (const auto& flatRange : flatRanges) {
            if (!areValidCharsInRange(flatRange)) {
                result.insert(ValidationResult::BadFlatRange);
            }
            auto limitsValidationResult = areValidLimitsInRange(
                flatRange, MIN_FLAT, MAX_FLAT);
            if (limitsValidationResult.outOfRange == OutOfRange::Yes) {
                result.insert(ValidationResult::FlatsOutOfRange);
            }
            if (limitsValidationResult.reverseOrder == ReverseOrder::Yes) {
                result.insert(ValidationResult::FlatsReverseOrder);
            }
            if (flatRange.size() > MAX_FLATS_PER_ENTRANCE) {
                result.insert(ValidationResult::TooManyFlatsPerEntrance);
            }
            for (size_t flatNum = 0; flatNum < flatRange.size(); ++flatNum) {
                const auto flatName = flatRange.value(flatNum);
                if (flatNames.contains(flatName)) {
                    result.insert(ValidationResult::FlatRangesIntersection);
                }
                flatNames.insert(flatName);
            }
        }

        if (flatLevelRange.levels.empty()) {
            continue;
        }

        Ranges levelRanges;
        try {
            levelRanges = parse(flatLevelRange.levels);
        } catch (ParseException&) {
            result.insert(ValidationResult::BadLevelRange);
        }
        if (levelRanges.size() > 1) {
            result.insert(ValidationResult::BadLevelRange);
        }
        for (const auto& levelRange : levelRanges) {
            if (!areValidCharsInRange(levelRange)) {
                result.insert(ValidationResult::BadLevelRange);
            }
            auto limitsValidationResult = areValidLimitsInRange(
                levelRange, MIN_LEVEL, MAX_LEVEL);
            if (limitsValidationResult.outOfRange == OutOfRange::Yes) {
                result.insert(ValidationResult::LevelsOutOfRange);
            }
            if (limitsValidationResult.reverseOrder == ReverseOrder::Yes) {
                result.insert(ValidationResult::LevelsReverseOrder);
            }
        }
    }

    return result;
}

} // namespace maps::wiki::flat_range
