#include <maps/wikimap/mapspro/libs/acl/include/schedule.h>
#include <maps/libs/json/include/value.h>

namespace maps::wiki::acl {

DaySequence
parseWorkRestDays(const std::string& json)
{
    DaySequence result;
    const auto jsonWorkRestDays = json::Value::fromString(json);
    ASSERT(jsonWorkRestDays.isArray());
    for (const auto& daysPair : jsonWorkRestDays) {
        ASSERT(daysPair.isArray() && daysPair.size() == 2);
        auto workDays = daysPair[0].as<int>();
        REQUIRE(workDays > 0, "Invalid work days count");
        auto restDays = daysPair[1].as<int>();
        REQUIRE(restDays > 0, "Invalid rest days count");
        result.emplace_back(workDays, restDays);
    }
    return result;
}

Schedule::Schedule(
    ID id,
    const std::string& startDate,
    const std::optional<std::string>& endDate,
    const std::optional<std::string>& startTime,
    const std::optional<std::string>& endTime,
    const std::optional<int>& weekdays,
    const std::optional<std::string>& workRestDays)
    : id_(id)
    , startDate_(startDate)
    , endDate_(endDate)
    , startTime_(startTime)
    , endTime_(endTime)
    , weekdays_( weekdays)
{
    REQUIRE((weekdays && !workRestDays) || (!weekdays && workRestDays),
        "Exactly one of weekdays and work/rest days sequence must be set");
    REQUIRE((startTime && endTime) || (!startTime && !endTime),
        "Start time and end time must be set or not set simultaneously");

    if (workRestDays) {
        workRestDays_ = parseWorkRestDays(*workRestDays);
    }
}

bool
Schedule::isActive(const chrono::TimePoint& atLocal) const
{
    auto startDateTp = chrono::parseIntegralDateTime(startDate_ + " 00:00", "%d.%m.%Y %H:%M");
    if (atLocal < startDateTp) {
        return false;
    }
    if (endDate_) {
        auto endDateTp = chrono::parseIntegralDateTime(*endDate_ + " 23:59", "%d.%m.%Y %H:%M");
        if (atLocal > endDateTp) {
            return false;
        }
    }
    if (startTime_) {
        auto startTimeTp = chrono::parseIntegralDateTime(
            chrono::formatIntegralDateTime(atLocal, "%d.%m.%Y") + " " + *startTime_,
            "%d.%m.%Y %H:%M");
        if (atLocal < startTimeTp) {
            return false;
        }
    }
    if (endTime_) {
        auto endTimeTp = chrono::parseIntegralDateTime(
            chrono::formatIntegralDateTime(atLocal, "%d.%m.%Y") + " " + *endTime_,
            "%d.%m.%Y %H:%M");
        if (atLocal > endTimeTp) {
            return false;
        }
    }
    if (weekdays_) {
        auto nowWeekday = std::stoi(chrono::formatIntegralDateTime(atLocal, "%u"));
        return (1 << (nowWeekday - 1)) & *weekdays_;
    }

    auto nowDaysDiff = std::chrono::duration_cast<std::chrono::days>(atLocal - startDateTp).count();

    int totalDays = 0;
    for (size_t i = 0; i < workRestDays_->size(); ++i) {
        totalDays += (*workRestDays_)[i].first + (*workRestDays_)[i].second;
    }

    nowDaysDiff = nowDaysDiff % totalDays;
    for (size_t i = 0; i < workRestDays_->size(); ++i) {
        if (nowDaysDiff < (*workRestDays_)[i].first) {
            return true;
        }
        nowDaysDiff -= (*workRestDays_)[i].first;
        if (nowDaysDiff < (*workRestDays_)[i].second) {
            return false;
        }
        nowDaysDiff -= (*workRestDays_)[i].second;
    }
    return false;
}

ScheduledPolicy::ScheduledPolicy(
    ID agentId, ID roleId, ID aoiId,
    const Schedule& schedule)
    : agentId_(agentId)
    , roleId_(roleId)
    , aoiId_(aoiId)
    , schedule_(schedule)
{
}

ScheduledGroup::ScheduledGroup(
    ID userId, ID groupId,
    const Schedule& schedule)
    : userId_(userId)
    , groupId_(groupId)
    , schedule_(schedule)
{
}

};
