// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <utils/date.hpp>

#include <generated/interfaces/kvs/Interface.hpp>

#define private public
// Make 'ReservationHandler.hpp privates public to test a helper function 'get_all_possible_orders'.
#include "ReservationHandler.hpp"
#undef private

using testing::_;
using testing::MockFunction;
using testing::Return;
using testing::SaveArg;

using namespace types::reservation;

namespace module {
class ReservationHandlerTest : public ::testing::Test {
private:
    uint32_t reservation_id = 0;

protected:
    Reservation create_reservation(const types::evse_manager::ConnectorTypeEnum connector_type) {
        return Reservation{static_cast<int32_t>(reservation_id),
                           "TOKEN_" + std::to_string(reservation_id++),
                           Everest::Date::to_rfc3339((date::utc_clock::now()) + std::chrono::hours(1)),
                           std::nullopt,
                           std::nullopt,
                           connector_type};
    }

    void add_connector(const int32_t evse_id, const uint32_t connector_id,
                       const types::evse_manager::ConnectorTypeEnum type,
                       std::map<int32_t, std::unique_ptr<EVSEContext>>& evses) {
        if (evses.count(evse_id) > 0) {
            evses[evse_id]->connectors.push_back(Connector{static_cast<int>(connector_id), type});
        } else {
            evses[evse_id] = std::make_unique<EVSEContext>(evse_id, evse_id - 1, connector_id, type);
        }
    }

    kvsIntf kvs;
    std::map<int32_t, std::unique_ptr<EVSEContext>> evses;
    ReservationHandler r{evses, "reservation_kvs", &kvs};
};

TEST_F(ReservationHandlerTest, global_reservation_scenario_01) {
    // Test global reservations (not bound to specific evse id). 3 EVSE's, one with cCCS2, two with cCCS2 and cType2.
    // Three cCCS2 reservations should be accepted.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_scenario_02) {
    // Test global reservations (not bound to specific evse id). 3 EVSE's, one with cCCS2, two with cCCS2 and cType2.
    // One cCCS2 and one cType2 reservation should be accepted, but another reservation can not be made. Because if
    // there would be two cCCS2 and one cType2, it is possible that first two cCCS2 type cars arrive, charge at the
    // two combined charging stations and when the cType2 car arrives, there is no charging station with this connector
    // available anymore.

    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_scenario_03) {
    // Test global reservations (not bound to specific evse id). 3 EVSE's, one with cCCS2, two with cCCS2 and cType2.
    // Two cType2 reservations should be accepted, but a third reservation is not accepted, because it is not guaranteed
    // that in all circumstances a charger is available (for example cCCS2 goes to evse 2, cType2 goes to connector
    // 1 and the second cType2 arrives but no charger is available anymore).
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_scenario_04) {
    // Test global reservations (not bound to specific evse id). 3 EVSE's, one with cCCS2, two with cCCS2 and cType2.
    // A cCCS2 and cType2 reservation should be accepted, because it does not matter in which order they arrive, there
    // is always an evse available for the other one. But a cType2 as third reservation is not possible. Imagine the
    // first car that arrives is cCCS2 and charges at evse 4 or 7, the second car can only put it at 4 or 7, then
    // the third car that arrives (cType2) does not have an EVSE for his type available.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(4, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(4, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);
    add_connector(7, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(7, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_scenario_05) {
    // Test global reservations (not bound to specific evse id). 4 EVSE's, three with cCCS2, one with cCCS2 and cType2.
    // When a cCCS2 reservation is made, cType2 can not make a reservation anymore, because it is possible that when
    // the cCCS2 car first arrives, there is no EVSE available for the cType2 car anymore (evse 2).
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);
    add_connector(3, 5, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
}

TEST_F(ReservationHandlerTest, global_reservation_scenario_06) {
    // Test global reservations (not bound to specific evse id). 3 EVSE's, two with cCCS2, one with cCCS2 and cType2.
    // Only one cType2 reservation can be made and nothing else, also no cCCS2 reservation (because when the cCCS2 car
    // arives first and puts it on connector 2, the cType2 that arrives second does not have an EVSE available anymore).
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_scenario_07) {
    // Test global reservations (not bound to specific evse id). 1 EVSE only. Unknown is accepted, a type that is not
    // available is rejected.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::sType3, evses);

    // There is no cType2 connector on this evse.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Rejected);
    // Unknown is accepted
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::Unknown)),
              ReservationResult::Accepted);
}

TEST_F(ReservationHandlerTest, global_reservation_scenario_08) {
    // Test global reservations (not bound to specific evse id). 3 EVSE's with all cCCS2 and cType2 connectors.
    // Unknown and cCCS2 reservations are accepted, max 3 in total.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::Unknown)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::Unknown)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::Unknown)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_scenario_09) {
    // Test global reservations (not bound to specific evse id). 3 EVSE's with all cCCS2 and cType2 connectors.
    // Unknown, cType2 and cCCS2 reservations are accepted, max 3 in total.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::Unknown)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_scenario_10) {
    // Test global reservations (not bound to specific evse id). 3 EVSE's with all two 'Unknown' connectors.
    // Three reservations are accepted in total, it does not matter what connector types they have.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::Unknown, evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::Unknown, evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::Unknown, evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::Unknown, evses);
    add_connector(5, 0, types::evse_manager::ConnectorTypeEnum::Unknown, evses);
    add_connector(5, 1, types::evse_manager::ConnectorTypeEnum::Unknown, evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::Unknown)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::Other3Ph)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_scenario_11) {
    // Test global reservations (not bound to specific evse id). 3 EVSE's with only one cCCS2 connector each.
    // In total three reservations are accepted with the correct type (cCCS2 or Unknown).
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::Unknown)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Rejected);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_scenario_12) {
    // Test global reservations (not bound to specific evse id). One EVSE with cCCS2 and cType2, one with cType2 and
    // cTesla, one with cTesla and cCCS2.
    // Only two reservations can be accepted, for the third there is no guarantee there is always place to charge in all
    // orders of arrival of the different cars.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cTesla, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cTesla, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType1)),
              ReservationResult::Rejected);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cTesla)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, get_all_possible_orders) {
    using namespace types::evse_manager;
    std::vector<ConnectorTypeEnum> connectors;
    connectors.push_back(ConnectorTypeEnum::cCCS2);

    std::vector<std::vector<ConnectorTypeEnum>> result = r.get_all_possible_orders(connectors);
    EXPECT_EQ(result, std::vector<std::vector<ConnectorTypeEnum>>({{ConnectorTypeEnum::cCCS2}}));

    connectors.push_back(ConnectorTypeEnum::cCCS2);
    result = r.get_all_possible_orders(connectors);
    EXPECT_EQ(result,
              std::vector<std::vector<ConnectorTypeEnum>>({{ConnectorTypeEnum::cCCS2, ConnectorTypeEnum::cCCS2}}));

    connectors.push_back(ConnectorTypeEnum::cCCS1);
    result = r.get_all_possible_orders(connectors);
    EXPECT_EQ(result, std::vector<std::vector<ConnectorTypeEnum>>(
                          {{ConnectorTypeEnum::cCCS2, ConnectorTypeEnum::cCCS2, ConnectorTypeEnum::cCCS1},
                           {ConnectorTypeEnum::cCCS2, ConnectorTypeEnum::cCCS1, ConnectorTypeEnum::cCCS2},
                           {ConnectorTypeEnum::cCCS1, ConnectorTypeEnum::cCCS2, ConnectorTypeEnum::cCCS2}}));
}

TEST_F(ReservationHandlerTest, get_all_possible_orders2) {
    using namespace types::evse_manager;
    std::vector<ConnectorTypeEnum> connectors;
    connectors.push_back(ConnectorTypeEnum::cType1);

    std::vector<std::vector<ConnectorTypeEnum>> result = r.get_all_possible_orders(connectors);
    EXPECT_EQ(result, std::vector<std::vector<ConnectorTypeEnum>>({{ConnectorTypeEnum::cType1}}));

    connectors.push_back(ConnectorTypeEnum::Pan);
    result = r.get_all_possible_orders(connectors);
    EXPECT_EQ(result,
              std::vector<std::vector<ConnectorTypeEnum>>({{ConnectorTypeEnum::cType1, ConnectorTypeEnum::Pan},
                                                           {ConnectorTypeEnum::Pan, ConnectorTypeEnum::cType1}}));

    connectors.push_back(ConnectorTypeEnum::cCCS1);
    result = r.get_all_possible_orders(connectors);
    EXPECT_EQ(result, std::vector<std::vector<ConnectorTypeEnum>>(
                          {{ConnectorTypeEnum::cType1, ConnectorTypeEnum::Pan, ConnectorTypeEnum::cCCS1},
                           {ConnectorTypeEnum::Pan, ConnectorTypeEnum::cCCS1, ConnectorTypeEnum::cType1},
                           {ConnectorTypeEnum::Pan, ConnectorTypeEnum::cType1, ConnectorTypeEnum::cCCS1},
                           {ConnectorTypeEnum::cType1, ConnectorTypeEnum::cCCS1, ConnectorTypeEnum::Pan},
                           {ConnectorTypeEnum::cCCS1, ConnectorTypeEnum::Pan, ConnectorTypeEnum::cType1},
                           {ConnectorTypeEnum::cCCS1, ConnectorTypeEnum::cType1, ConnectorTypeEnum::Pan}}));
}

TEST_F(ReservationHandlerTest, specific_evse_scenario_01) {
    // Test reservations for a specific evse.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    // On EVSE1, there is no cCCS1 type connector.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS1)),
              ReservationResult::Rejected);
    // But there is a cCCS2 type connector, accept reservation.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    // Another reservation on cCCS1 type connector will return Occupied, as there already is a reservation.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    // But on another connector, the reservation can be made.
    EXPECT_EQ(r.make_reservation(0, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(2, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    // But only when there is not already a reservation for that specific connector.
    EXPECT_EQ(r.make_reservation(2, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, specific_evse_scenario_02) {
    // Test reservations for a specific evse.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    // No cCCS2 type on evse 1 (only on 0, but that one is not reserved here).
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Rejected);
    // But it has a cType2 connector.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    // There already is a reservation for this EVSE, so 'Occupied' is returned.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    // But on the other EVSE, the reservation can be made.
    EXPECT_EQ(r.make_reservation(0, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    // And now it is already reserved, so a second can not be made.
    EXPECT_EQ(r.make_reservation(0, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_specific_evse_combination_scenario_01) {
    // Test global reservation (not bound to specific EVSE) combined with reservation for a specific EVSE.
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);

    // Global reservation for cType2, this can be EVSE 0 or 1.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    // Specific reservation for EVSE 1, the global reservation can still charge on EVSE 0.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    // There already is a reservation for EVSE 1.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    // Specific reservation for EVSE 2, the global reservation can still charge on EVSE 0.
    EXPECT_EQ(r.make_reservation(2, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    // Specific reservation for EVSE 0, but if this would be accepted, the global reservation can not charge anymore, so
    // this is denied.
    EXPECT_EQ(r.make_reservation(0, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    // EVSE 1 is already occupied with a reservation.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    // Global reservation, can not be made because then the first reservation can not charge anymore.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    // Same for a cCCS2 global reservation.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_specific_evse_combination_scenario_02) {
    // Test global reservation (not bound to specific EVSE) combined with reservation for a specific EVSE.
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);

    // Global reservation for cType2, this can charge on EVSE 0 or 1.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    // Specific reservation for EVSE 1, the global reservation still has an EVSE left to charge on.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    // EVSE 1 is already reserved.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    // Another global reservation. This can not be made, because the first global reservation would not have been able
    // to charge in that case.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    // But a global reservation for cCCS2 is possible. Because as EVSE 1 is reserved, there is only one option left for
    // this reservation, which is EVSE 2, and the first reservation can then still charge on EVSE 0.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    // But another global reservation is not possible anymore.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    // As well as any other specific reservation.
    EXPECT_EQ(r.make_reservation(2, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(0, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, global_reservation_specific_evse_combination_scenario_03) {
    // Test global reservation (not bound to specific EVSE) combined with reservation for a specific EVSE.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(3, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    // Make a reservation for EVSE 2.
    EXPECT_EQ(r.make_reservation(2, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    // EVSE 2 already has a reservation.
    EXPECT_EQ(r.make_reservation(2, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    // Make a reservation for EVSE 1.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    // A global reservation is possible, because EVSE 0 is still not reserved and has cCCS2.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    // But another global reservation is not possible, because there are not enough cCCS2 connectors.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);
    // And cType2 is also not possible, because it can arrive before the first global reservation and then put the car
    // at EVSE 0, and then there will be no place for the car that did the first global reservation.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    // But a specific reservation for EVSE 3 is possible, because the first global reservation can then still charge
    // at EVSE 0.
    EXPECT_EQ(r.make_reservation(3, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
}

TEST_F(ReservationHandlerTest, check_charging_possible_global_specific_reservations_scenario_01) {
    // Do some specific reservations and check if charging is possible when a car arrives.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(3, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    // Charging is possible on all EVSE's (except for the not existing one of course).
    EXPECT_TRUE(r.is_charging_possible(0));
    EXPECT_TRUE(r.is_charging_possible(2));
    EXPECT_TRUE(r.is_charging_possible(1));
    EXPECT_TRUE(r.is_charging_possible(3));
    EXPECT_FALSE(r.is_charging_possible(4));
    // But after a reservation on EVSE 2, charging is not possible on that EVSE anymore.
    EXPECT_EQ(r.make_reservation(2, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_TRUE(r.is_charging_possible(0));
    EXPECT_FALSE(r.is_charging_possible(2));
    EXPECT_TRUE(r.is_charging_possible(1));
    EXPECT_TRUE(r.is_charging_possible(3));
    // Now EVSE 1 is also occupied, charging will also not be possible there.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_TRUE(r.is_charging_possible(0));
    EXPECT_FALSE(r.is_charging_possible(2));
    EXPECT_FALSE(r.is_charging_possible(1));
    EXPECT_TRUE(r.is_charging_possible(3));
    // A global reservation have been made for a cCCS2 charger. The only still available is the one on EVSE 0. That one
    // must be available for the reservation at all times.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    // So that makes charging on EVSE 0 not possible.
    EXPECT_FALSE(r.is_charging_possible(0));
    // But the car can charge at EVSE 3 (as EVSE 0 is then still available for the global reservation).
    EXPECT_TRUE(r.is_charging_possible(3));
    // And now all reservations are made, no new car can make a reservation or charge anymore.
    EXPECT_EQ(r.make_reservation(3, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_FALSE(r.is_charging_possible(0));
    EXPECT_FALSE(r.is_charging_possible(1));
    EXPECT_FALSE(r.is_charging_possible(2));
    EXPECT_FALSE(r.is_charging_possible(3));
}

TEST_F(ReservationHandlerTest, check_charging_possible_global_specific_reservations_scenario_02) {
    // Do some specific reservations and check if charging is possible when a car arrives.
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);

    // Charging is possible on all EVSE's.
    EXPECT_TRUE(r.is_charging_possible(0));
    EXPECT_TRUE(r.is_charging_possible(1));
    EXPECT_TRUE(r.is_charging_possible(2));
    // After a global reservation for cType2, charging is still possible on all EVSE's, is there are two cType2
    // connectors, so when one car charges, there is still a cType2 available.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_TRUE(r.is_charging_possible(0));
    EXPECT_TRUE(r.is_charging_possible(1));
    EXPECT_TRUE(r.is_charging_possible(2));
    // A reservation for EVSE 1 is made. Now the global reservation only has the possibility to charge on EVSE 0.
    // So on that EVSE, charging is not possible anymore. And of course also not on EVSE 1 as that one is reserved.
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_FALSE(r.is_charging_possible(0));
    EXPECT_FALSE(r.is_charging_possible(1));
    EXPECT_TRUE(r.is_charging_possible(2));
    // Another global reservation makes charging impossible on all EVSE's.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_FALSE(r.is_charging_possible(0));
    EXPECT_FALSE(r.is_charging_possible(1));
    EXPECT_FALSE(r.is_charging_possible(2));
}

TEST_F(ReservationHandlerTest, is_evse_reserved) {
    // Check if a specific EVSE is reserved.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    EXPECT_FALSE(r.is_evse_reserved(0));
    EXPECT_FALSE(r.is_evse_reserved(1));

    // After a global reservation, no specific EVSE is still reserved.
    r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2));

    EXPECT_FALSE(r.is_evse_reserved(0));
    EXPECT_FALSE(r.is_evse_reserved(1));

    // But after a specific reservation, the EVSE of that reservation is reserved.
    r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2));

    EXPECT_FALSE(r.is_evse_reserved(0));
    EXPECT_TRUE(r.is_evse_reserved(1));
}

TEST_F(ReservationHandlerTest, change_availability_scenario_01) {
    // Change availability of an EVSE and check if reservations are cancelled.
    std::optional<uint32_t> evse_id;
    MockFunction<void(const std::optional<uint32_t>& evse_id, const int32_t reservation_id,
                      const ReservationEndReason reason, const bool send_reservation_update)>
        reservation_callback_mock;

    r.register_reservation_cancelled_callback(reservation_callback_mock.AsStdFunction());
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(3, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    // Four global reservations are made.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);

    // Set an evse to not available, this will call the cancel reservation callback for the last reserved reservation
    // id
    EXPECT_CALL(reservation_callback_mock, Call(_, 3, ReservationEndReason::Cancelled, true))
        .WillOnce(SaveArg<0>(&evse_id));

    this->evses[1]->connectors.at(1).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[1]->connectors.at(1).get_state(), 1, 1);
    EXPECT_FALSE(evse_id.has_value());

    // Setting an evse to faulted will cancel the next reservation.
    EXPECT_CALL(reservation_callback_mock, Call(_, 2, ReservationEndReason::Cancelled, true))
        .WillOnce(SaveArg<0>(&evse_id));

    this->evses[3]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[3]->connectors.at(0).get_state(), 3, 1);
    EXPECT_FALSE(evse_id.has_value());

    // Set evse to available again. This will not call a cancelled callback. And setting one to unavailable will also
    // not cause the cancelled callback to be called because there is still one evse available.
    EXPECT_CALL(reservation_callback_mock, Call(_, 2, ReservationEndReason::Cancelled, true)).Times(0);

    this->evses[3]->connectors.at(0).submit_event(ConnectorEvent::ERROR_CLEARED);
    r.on_connector_state_changed(this->evses[3]->connectors.at(0).get_state(), 3, 1);

    this->evses[2]->connectors.at(1).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[2]->connectors.at(1).get_state(), 2, 1);

    // If we set even one more evse to unavailable (or actually, to faulted), this will cancel the next (or actually
    // previous) reservation.
    EXPECT_CALL(reservation_callback_mock, Call(_, 1, ReservationEndReason::Cancelled, true))
        .WillOnce(SaveArg<0>(&evse_id));

    this->evses[0]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[0]->connectors.at(1).get_state(), 0, 1);
    EXPECT_FALSE(evse_id.has_value());
}

TEST_F(ReservationHandlerTest, change_availability_scenario_02) {
    // Change availability of an EVSE and check if reservations are cancelled. This time, global and specific EVSE
    // reservations mixed.
    std::optional<uint32_t> evse_id;
    MockFunction<void(const std::optional<uint32_t>& evse_id, const int32_t reservation_id,
                      const ReservationEndReason reason, const bool send_reservation_update)>
        reservation_callback_mock;

    r.register_reservation_cancelled_callback(reservation_callback_mock.AsStdFunction());
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(3, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(3, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);

    // Set an evse to not available, this will call the cancel reservation callback for the reservation of that evse id.
    EXPECT_CALL(reservation_callback_mock, Call(_, 0, ReservationEndReason::Cancelled, true))
        .WillOnce(SaveArg<0>(&evse_id));
    this->evses[1]->connectors.at(1).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[1]->connectors.at(1).get_state(), 1, 1);
    ASSERT_TRUE(evse_id.has_value());
    EXPECT_EQ(evse_id.value(), 1);

    // Setting an evse to faulted will cancel the next reservation (last made), this will be a 'global' reservation as
    // there is no evse specific reservation made.
    EXPECT_CALL(reservation_callback_mock, Call(_, 3, ReservationEndReason::Cancelled, true))
        .WillOnce(SaveArg<0>(&evse_id));
    this->evses[2]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[2]->connectors.at(1).get_state(), 2, 1);
    EXPECT_FALSE(evse_id.has_value());

    // Set one more evse to unavailable, this will cancel the next reservation.
    EXPECT_CALL(reservation_callback_mock, Call(_, 2, ReservationEndReason::Cancelled, true))
        .WillOnce(SaveArg<0>(&evse_id));
    this->evses[0]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[0]->connectors.at(1).get_state(), 0, 1);

    // r.set_evse_state(ConnectorState::FAULTED, 0);
    EXPECT_FALSE(evse_id.has_value());

    // Set the last evse to unavailable will cancel the reservation of that specific evse.
    EXPECT_CALL(reservation_callback_mock, Call(_, 1, ReservationEndReason::Cancelled, true))
        .WillOnce(SaveArg<0>(&evse_id));

    this->evses[3]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[3]->connectors.at(0).get_state(), 3, 1);
    ASSERT_TRUE(evse_id.has_value());
    EXPECT_EQ(evse_id.value(), 3);
}

TEST_F(ReservationHandlerTest, reservation_evse_unavailable) {
    // Set evse unavailable and check if a reservation can not be made in that case. Global reservations.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(3, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    this->evses[1]->connectors.at(0).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[1]->connectors.at(0).get_state(), 1, 0);
    this->evses[1]->connectors.at(1).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[1]->connectors.at(1).get_state(), 1, 1);
    // r.set_evse_state(ConnectorState::UNAVAILABLE, 1);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);

    this->evses[0]->connectors.at(0).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[0]->connectors.at(0).get_state(), 0, 0);
    this->evses[0]->connectors.at(1).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[0]->connectors.at(0).get_state(), 0, 1);
    this->evses[2]->connectors.at(0).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[2]->connectors.at(0).get_state(), 2, 0);
    this->evses[2]->connectors.at(1).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[2]->connectors.at(0).get_state(), 2, 1);
    this->evses[3]->connectors.at(0).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[3]->connectors.at(0).get_state(), 3, 1);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Unavailable);
}

TEST_F(ReservationHandlerTest, reservation_specific_evse_unavailable) {
    // Set an EVSE to unavailable and check if that specific EVSE can not be reserved anymore.

    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    this->evses[1]->connectors.at(0).submit_event(ConnectorEvent::DISABLE);
    this->evses[1]->connectors.at(1).submit_event(ConnectorEvent::DISABLE);

    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Unavailable);
}

TEST_F(ReservationHandlerTest, reservation_specific_evse_faulted) {
    // Set an EVSE to faulted and check if that specific EVSE can not be reserved anymore.

    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    // Evse state is faulted, should return faulted.
    this->evses[0]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[0]->connectors.at(0).get_state(), 0, 0);
    this->evses[0]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[0]->connectors.at(1).get_state(), 0, 1);

    EXPECT_EQ(r.make_reservation(0, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Faulted);

    // Connectors are faulted, should return faulted.
    this->evses[1]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[1]->connectors.at(0).get_state(), 1, 0);
    this->evses[1]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[1]->connectors.at(1).get_state(), 1, 1);

    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Faulted);
}

TEST_F(ReservationHandlerTest, reservation_evse_faulted) {
    // Set EVSE's to faulted and check if no global reservations can made for that EVSE.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(3, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    this->evses[1]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[1]->connectors.at(0).get_state(), 1, 0);
    this->evses[1]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[1]->connectors.at(1).get_state(), 1, 1);

    // One EVSE is faulted and there are only two cCCS2 connectors left. So only two global reservations for cCCS2 can
    // be made.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);

    // Everything is faulted now, a reservation is not possible anymore.
    this->evses[0]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[0]->connectors.at(0).get_state(), 0, 0);
    this->evses[0]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[0]->connectors.at(1).get_state(), 0, 1);
    this->evses[2]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[2]->connectors.at(0).get_state(), 2, 0);
    this->evses[2]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[2]->connectors.at(1).get_state(), 2, 1);
    this->evses[3]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[3]->connectors.at(0).get_state(), 3, 1);

    // All EVSE's are faulted, so 'Faulted' is returned.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Faulted);
}

TEST_F(ReservationHandlerTest, reservation_evse_unavailable_and_faulted) {
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(3, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    // Set evse to faulted.
    this->evses[1]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[1]->connectors.at(0).get_state(), 1, 0);
    this->evses[1]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[1]->connectors.at(1).get_state(), 1, 1);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Occupied);

    // Set all other evse's to unavailable, but not faulted.
    this->evses[0]->connectors.at(0).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[0]->connectors.at(0).get_state(), 0, 0);
    this->evses[0]->connectors.at(1).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[0]->connectors.at(1).get_state(), 0, 1);
    this->evses[2]->connectors.at(0).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[2]->connectors.at(0).get_state(), 2, 0);
    this->evses[2]->connectors.at(1).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[2]->connectors.at(1).get_state(), 2, 1);
    this->evses[3]->connectors.at(0).submit_event(ConnectorEvent::DISABLE);
    r.on_connector_state_changed(this->evses[3]->connectors.at(0).get_state(), 3, 1);

    // At least one evse is faulted, so 'faulted' is returned.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Faulted);
}

TEST_F(ReservationHandlerTest, reservation_connector_all_faulted) {
    // Set all connectors to 'Faulted', no reservation can be made and the function will return 'Faulted'.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(3, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(3, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    this->evses[0]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    this->evses[0]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);
    this->evses[3]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    this->evses[3]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Faulted);
}

TEST_F(ReservationHandlerTest, reservation_connector_unavailable) {
    // Set specific connectors to 'Unavailable' and try to make reservations.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS1, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType1, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    this->evses[0]->connectors.at(0).submit_event(ConnectorEvent::DISABLE);
    this->evses[1]->connectors.at(0).submit_event(ConnectorEvent::DISABLE);
    this->evses[1]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);

    // There is a reservation already made, so this will return 'occupied'.
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS1)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType1)),
              ReservationResult::Occupied);
}

TEST_F(ReservationHandlerTest, reservation_in_the_past) {
    // Try to create a reservation in the past, this should be rejected.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    Reservation reservation = create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2);
    reservation.expiry_time = Everest::Date::to_rfc3339(date::utc_clock::now() - std::chrono::hours(2));
    EXPECT_EQ(r.make_reservation(std::nullopt, reservation), ReservationResult::Rejected);
}

TEST_F(ReservationHandlerTest, reservation_timer) {
    // Test the reservation timer: after the time has expired, the reservation should be cancelled.
    std::optional<uint32_t> evse_id;
    MockFunction<void(const std::optional<uint32_t>& evse_id, const int32_t reservation_id,
                      const ReservationEndReason reason, const bool send_reservation_update)>
        reservation_callback_mock;

    r.register_reservation_cancelled_callback(reservation_callback_mock.AsStdFunction());

    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    EXPECT_CALL(reservation_callback_mock, Call(_, 0, ReservationEndReason::Expired, true))
        .WillOnce(SaveArg<0>(&evse_id));
    Reservation reservation = create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2);
    reservation.expiry_time = Everest::Date::to_rfc3339(date::utc_clock::now() + std::chrono::seconds(1));
    EXPECT_EQ(r.make_reservation(std::nullopt, reservation), ReservationResult::Accepted);
    sleep(2);
    EXPECT_FALSE(evse_id.has_value());

    EXPECT_CALL(reservation_callback_mock, Call(_, 0, ReservationEndReason::Expired, true))
        .WillOnce(SaveArg<0>(&evse_id));
    reservation.expiry_time = Everest::Date::to_rfc3339(date::utc_clock::now() + std::chrono::seconds(1));
    EXPECT_EQ(r.make_reservation(0, reservation), ReservationResult::Accepted);
    sleep(2);
    ASSERT_TRUE(evse_id.has_value());
    EXPECT_EQ(evse_id.value(), 0);
}

TEST_F(ReservationHandlerTest, cancel_reservation) {
    // Cancel reservation and check if a new reservation can be made after an old one is cancelled.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);

    std::pair<bool, std::optional<uint32_t>> reservation_cancelled_check_value;

    // There was no reservation with id 5.
    reservation_cancelled_check_value = {false, std::nullopt};
    EXPECT_EQ(r.cancel_reservation(5, false, ReservationEndReason::Cancelled), reservation_cancelled_check_value);

    // There was a reservation with id 1, it had no evse id (global reservation).
    reservation_cancelled_check_value = {true, std::nullopt};
    EXPECT_EQ(r.cancel_reservation(1, false, ReservationEndReason::Cancelled), reservation_cancelled_check_value);

    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);

    // There was a reservation with id 3, it was made for evse id 1.
    reservation_cancelled_check_value = {true, 1};
    EXPECT_EQ(r.cancel_reservation(3, false, ReservationEndReason::Cancelled), reservation_cancelled_check_value);
}

TEST_F(ReservationHandlerTest, overwrite_reservation) {
    // If a reservation is made and another one is made with the same reservation id, it should be overwritten.
    // The old reservation will then be cancelled and the new one is made.
    MockFunction<void(const std::optional<uint32_t>& evse_id, const int32_t reservation_id,
                      const ReservationEndReason reason, const bool send_reservation_update)>
        reservation_callback_mock;

    r.register_reservation_cancelled_callback(reservation_callback_mock.AsStdFunction());

    add_connector(5, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(5, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    EXPECT_CALL(reservation_callback_mock, Call(_, 0, ReservationEndReason::Cancelled, false)).Times(0);

    Reservation reservation = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    EXPECT_EQ(r.make_reservation(5, reservation), ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(5, reservation), ReservationResult::Accepted);
}

TEST_F(ReservationHandlerTest, matches_reserved_identifier) {
    // Check if token or parent token matches with a reservation.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(2, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(2, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    Reservation reservation = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    reservation.parent_id_token = "PARENT_TOKEN_0";
    Reservation reservation2 = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    reservation2.parent_id_token = "PARENT_TOKEN_2";
    Reservation reservation3 = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    reservation3.parent_id_token = "PARENT_TOKEN_3";
    EXPECT_EQ(r.make_reservation(std::nullopt, reservation), ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(1, reservation2), ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(2, reservation3), ReservationResult::Accepted);

    // Id token is correct and evse id as well.
    EXPECT_EQ(r.matches_reserved_identifier(reservation.id_token, std::nullopt, std::nullopt), 0);
    // Id token is correct and evse id as well, parent token is not but that is ignored since the normal token is ok.
    EXPECT_EQ(r.matches_reserved_identifier(reservation.id_token, std::nullopt, "WRONG_PARENT_TOKEN"), 0);
    // Token is wrong.
    EXPECT_EQ(r.matches_reserved_identifier("WRONG_TOKEN", std::nullopt, std::nullopt), std::nullopt);
    // Evse id reservation does not have parent token, do not search in global reservation.
    EXPECT_EQ(r.matches_reserved_identifier(reservation.id_token, 1, std::nullopt), std::nullopt);
    // Evse id is wrong.
    EXPECT_EQ(r.matches_reserved_identifier(reservation2.id_token, 2, std::nullopt), std::nullopt);
    // Token is wrong but parent token is correct.
    EXPECT_EQ(r.matches_reserved_identifier("WRONG_TOKEN", std::nullopt, "PARENT_TOKEN_0"), 0);
    // Token is wrong and parent token as well.
    EXPECT_EQ(r.matches_reserved_identifier("WRONG_TOKEN", std::nullopt, "WRONG_PARENT_TOKEN"), std::nullopt);
    // Evse id is correct and token is correct.
    EXPECT_EQ(r.matches_reserved_identifier(reservation2.id_token, 1, std::nullopt), 1);
    // Evse id is correct but token is wrong.
    EXPECT_EQ(r.matches_reserved_identifier("TOKEN_NOK", 1, std::nullopt), std::nullopt);
    // Evse id is wrong and token is correct.
    EXPECT_EQ(r.matches_reserved_identifier(reservation2.id_token, 2, std::nullopt), std::nullopt);
    // Evse id is correct, token is wrong but parent token is correct.
    EXPECT_EQ(r.matches_reserved_identifier("TOKEN_NOK", 1, "PARENT_TOKEN_2"), 1);
    // Evse id is correct, token is wrong and parent token as well.
    EXPECT_EQ(r.matches_reserved_identifier("TOKEN_NOK", 1, "PARENT_TOKEN_NOK"), std::nullopt);
}

TEST_F(ReservationHandlerTest, has_reservation_parent_id) {
    // Check if the reservation has a parent id token.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    Reservation reservation = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    reservation.parent_id_token = "PARENT_TOKEN_0";
    Reservation reservation2 = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    reservation2.parent_id_token = "PARENT_TOKEN_2";
    EXPECT_EQ(r.make_reservation(std::nullopt, reservation), ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(1, reservation2), ReservationResult::Accepted);

    // Id token is correct and evse id as well.
    EXPECT_TRUE(r.has_reservation_parent_id(std::nullopt));
    EXPECT_TRUE(r.has_reservation_parent_id(1));
    EXPECT_TRUE(r.has_reservation_parent_id(0));
    // Evse id does not exist.
    EXPECT_FALSE(r.has_reservation_parent_id(2));
}

TEST_F(ReservationHandlerTest, has_reservation_parent_id_no_parent_token) {
    // Check if the reservation has a parent id token.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    Reservation reservation = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    Reservation reservation2 = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    EXPECT_EQ(r.make_reservation(std::nullopt, reservation), ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(1, reservation2), ReservationResult::Accepted);

    // No parent id tokens
    EXPECT_FALSE(r.has_reservation_parent_id(std::nullopt));
    EXPECT_FALSE(r.has_reservation_parent_id(1));
    EXPECT_FALSE(r.has_reservation_parent_id(0));
    // Evse id does not exist.
    EXPECT_FALSE(r.has_reservation_parent_id(2));
}

TEST_F(ReservationHandlerTest, has_reservation_parent_id_evse_reservation_parent_token) {
    // Check if the reservation has a parent id token.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    Reservation reservation = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    Reservation reservation2 = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    reservation2.parent_id_token = "PARENT_TOKEN_2";
    EXPECT_EQ(r.make_reservation(std::nullopt, reservation), ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(1, reservation2), ReservationResult::Accepted);

    // Only evse id 1 reservation has parent id token.
    EXPECT_FALSE(r.has_reservation_parent_id(std::nullopt));
    EXPECT_TRUE(r.has_reservation_parent_id(1));
    // So evse id 0 has not.
    EXPECT_FALSE(r.has_reservation_parent_id(0));
    // Evse id does not exist.
    EXPECT_FALSE(r.has_reservation_parent_id(2));
}

TEST_F(ReservationHandlerTest, has_reservation_parent_id_global_reservation_parent_token) {
    // Check if the reservation has a parent id token.
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    Reservation reservation = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    reservation.parent_id_token = "PARENT_TOKEN_0";
    Reservation reservation2 = create_reservation(types::evse_manager::ConnectorTypeEnum::cType2);
    EXPECT_EQ(r.make_reservation(std::nullopt, reservation), ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(1, reservation2), ReservationResult::Accepted);

    // Only global reservation has parent id token. Reservation on evse id 1 has none.
    EXPECT_TRUE(r.has_reservation_parent_id(std::nullopt));
    EXPECT_FALSE(r.has_reservation_parent_id(1));
    // No reservation for evse id 0, but global reservation has parent id token.
    EXPECT_TRUE(r.has_reservation_parent_id(0));
    // Evse id does not exist.
    EXPECT_FALSE(r.has_reservation_parent_id(2));
}

TEST_F(ReservationHandlerTest, on_reservation_used) {
    // A reservation is made and later used, so the reservation should be removed and the EVSE available again.

    // Register a callback, which should not be called.
    MockFunction<void(const std::optional<uint32_t>& evse_id, const int32_t reservation_id,
                      const ReservationEndReason reason, const bool send_reservation_update)>
        reservation_callback_mock;

    r.register_reservation_cancelled_callback(reservation_callback_mock.AsStdFunction());

    EXPECT_CALL(reservation_callback_mock, Call(_, _, _, true)).Times(0);

    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);

    r.on_reservation_used(1);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);
    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Occupied);

    r.on_reservation_used(0);
    r.on_reservation_used(2);
    r.on_reservation_used(3);

    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
}

TEST_F(ReservationHandlerTest, store_load_reservations) {
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    EXPECT_TRUE(r.evse_reservations.empty());
    EXPECT_TRUE(r.global_reservations.empty());

    r.load_reservations();

    EXPECT_TRUE(r.evse_reservations.empty());
    EXPECT_TRUE(r.global_reservations.empty());

    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);

    EXPECT_EQ(r.evse_reservations.size(), 1);
    EXPECT_EQ(r.global_reservations.size(), 1);
    EXPECT_EQ(r.reservation_id_to_reservation_timeout_timer_map.size(), 2);

    r.evse_reservations.clear();
    r.global_reservations.clear();
    r.reservation_id_to_reservation_timeout_timer_map.clear();

    EXPECT_TRUE(r.evse_reservations.empty());
    EXPECT_TRUE(r.global_reservations.empty());
    EXPECT_TRUE(r.reservation_id_to_reservation_timeout_timer_map.empty());

    r.load_reservations();

    EXPECT_EQ(r.evse_reservations.size(), 1);
    EXPECT_EQ(r.global_reservations.size(), 1);
    EXPECT_EQ(r.reservation_id_to_reservation_timeout_timer_map.size(), 2);
}

TEST_F(ReservationHandlerTest, store_load_reservations_connector_unavailable) {
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    // Register a callback, which should not be called.
    MockFunction<void(const std::optional<uint32_t>& evse_id, const int32_t reservation_id,
                      const ReservationEndReason reason, const bool send_reservation_update)>
        reservation_callback_mock;

    r.register_reservation_cancelled_callback(reservation_callback_mock.AsStdFunction());

    EXPECT_CALL(reservation_callback_mock, Call(_, _, _, true)).Times(1);

    EXPECT_TRUE(r.evse_reservations.empty());
    EXPECT_TRUE(r.global_reservations.empty());

    r.load_reservations();

    EXPECT_TRUE(r.evse_reservations.empty());
    EXPECT_TRUE(r.global_reservations.empty());

    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);
    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);

    EXPECT_EQ(r.evse_reservations.size(), 1);
    EXPECT_EQ(r.global_reservations.size(), 1);
    EXPECT_EQ(r.reservation_id_to_reservation_timeout_timer_map.size(), 2);

    r.evse_reservations.clear();
    r.global_reservations.clear();
    r.reservation_id_to_reservation_timeout_timer_map.clear();

    EXPECT_TRUE(r.evse_reservations.empty());
    EXPECT_TRUE(r.global_reservations.empty());
    EXPECT_TRUE(r.reservation_id_to_reservation_timeout_timer_map.empty());

    this->evses[1]->connectors.at(0).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[1]->connectors.at(0).get_state(), 3, 0);
    this->evses[1]->connectors.at(1).submit_event(ConnectorEvent::FAULTED);
    r.on_connector_state_changed(this->evses[1]->connectors.at(1).get_state(), 1, 1);

    r.load_reservations();

    EXPECT_EQ(r.evse_reservations.size(), 0);
    EXPECT_EQ(r.global_reservations.size(), 1);
    EXPECT_EQ(r.reservation_id_to_reservation_timeout_timer_map.size(), 1);
}

TEST_F(ReservationHandlerTest, check_evses_to_reserve_scenario_1) {
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    ReservationEvseStatus s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    EXPECT_TRUE(s.reserved.empty());

    EXPECT_EQ(r.make_reservation(1, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);

    s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    EXPECT_TRUE(s.reserved.empty());

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);

    s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    ASSERT_EQ(s.reserved.size(), 1);
    EXPECT_EQ(s.reserved.count(0), 1);
}

TEST_F(ReservationHandlerTest, check_evses_to_reserve_scenario_2) {
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    ReservationEvseStatus s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    EXPECT_TRUE(s.reserved.empty());

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);

    s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    EXPECT_TRUE(s.reserved.empty());

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);

    s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    ASSERT_EQ(s.reserved.size(), 2);
    EXPECT_EQ(s.reserved.count(0), 1);
    EXPECT_EQ(s.reserved.count(1), 1);
}

TEST_F(ReservationHandlerTest, check_evses_to_reserve_scenario_3) {
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    ReservationEvseStatus s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    EXPECT_TRUE(s.reserved.empty());

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);

    s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    ASSERT_EQ(s.reserved.size(), 1);
    EXPECT_EQ(s.reserved.count(1), 1);
}

TEST_F(ReservationHandlerTest, check_evses_to_reserve_scenario_4) {
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    this->evses.at(0)->plugged_in = true;

    ReservationEvseStatus s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    EXPECT_TRUE(s.reserved.empty());

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);

    s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    ASSERT_EQ(s.reserved.size(), 1);
    EXPECT_EQ(s.reserved.count(1), 1);

    this->evses.at(0)->plugged_in = false;

    s = r.check_number_global_reservations_match_number_available_evses();

    ASSERT_EQ(s.available.size(), 1);
    EXPECT_EQ(s.available.count(1), 1);
    EXPECT_TRUE(s.reserved.empty());
}

TEST_F(ReservationHandlerTest, check_evses_to_reserve_scenario_5) {
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    this->evses.at(1)->connectors.at(0).submit_event(ConnectorEvent::TRANSACTION_STARTED);

    ReservationEvseStatus s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    EXPECT_TRUE(s.reserved.empty());

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cType2)),
              ReservationResult::Accepted);

    s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    ASSERT_EQ(s.reserved.size(), 1);
    EXPECT_EQ(s.reserved.count(0), 1);

    this->evses.at(1)->connectors.at(0).submit_event(ConnectorEvent::SESSION_FINISHED);

    s = r.check_number_global_reservations_match_number_available_evses();

    ASSERT_EQ(s.available.size(), 1);
    EXPECT_EQ(s.available.count(0), 1);
    EXPECT_TRUE(s.reserved.empty());
}

TEST_F(ReservationHandlerTest, check_evses_to_reserve_scenario_6) {
    add_connector(0, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(0, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);
    add_connector(1, 0, types::evse_manager::ConnectorTypeEnum::cCCS2, this->evses);
    add_connector(1, 1, types::evse_manager::ConnectorTypeEnum::cType2, this->evses);

    ReservationEvseStatus s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    EXPECT_TRUE(s.reserved.empty());

    Reservation reservation1 = create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2);
    EXPECT_EQ(r.make_reservation(std::nullopt, reservation1), ReservationResult::Accepted);

    s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    EXPECT_TRUE(s.reserved.empty());

    EXPECT_EQ(r.make_reservation(std::nullopt, create_reservation(types::evse_manager::ConnectorTypeEnum::cCCS2)),
              ReservationResult::Accepted);

    s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_TRUE(s.available.empty());
    ASSERT_EQ(s.reserved.size(), 2);
    EXPECT_EQ(s.reserved.count(0), 1);
    EXPECT_EQ(s.reserved.count(1), 1);

    r.cancel_reservation(reservation1.reservation_id, false, types::reservation::ReservationEndReason::Cancelled);

    s = r.check_number_global_reservations_match_number_available_evses();

    EXPECT_EQ(s.available.size(), 2);
    EXPECT_EQ(s.reserved.size(), 0);
}

} // namespace module
