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

#include <everest/util/vector/fixed_vector.hpp>

#include <gtest/gtest.h>

#include <memory>
#include <string>

using namespace everest::lib::util;

TEST(FixedVectorTest, BasicInt) {
    fixed_vector<int, 10> vec;
    EXPECT_TRUE(vec.empty());
    EXPECT_EQ(vec.size(), 0);

    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);

    EXPECT_FALSE(vec.empty());
    EXPECT_EQ(vec.size(), 3);
    EXPECT_EQ(vec[0], 1);
    EXPECT_EQ(vec[1], 2);
    EXPECT_EQ(vec[2], 3);
    EXPECT_EQ(vec.at(1), 2);

    int count = 1;
    for (const auto& val : vec) {
        EXPECT_EQ(val, count++);
    }

    vec.clear();
    EXPECT_TRUE(vec.empty());
    EXPECT_EQ(vec.size(), 0);
}

TEST(FixedVectorTest, StringAndMove) {
    fixed_vector<std::string, 5> vec;
    vec.push_back("hello");
    std::string s = "world";
    vec.push_back(std::move(s));

    EXPECT_EQ(vec.size(), 2);
    EXPECT_EQ(vec[0], "hello");
    EXPECT_EQ(vec[1], "world");
    // Note: The state of a moved-from string is valid but unspecified.
    // In many implementations it is empty, but we shouldn't rely on it.
    // EXPECT_TRUE(s.empty());

    vec.emplace_back(5, 'c');
    EXPECT_EQ(vec.size(), 3);
    EXPECT_EQ(vec[2], "ccccc");
}

TEST(FixedVectorTest, Capacity) {
    fixed_vector<int, 3> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);
    EXPECT_EQ(vec.size(), 3);
    EXPECT_THROW(vec.push_back(4), std::length_error);
    EXPECT_THROW(vec.emplace_back(5), std::length_error);
}

TEST(FixedVectorTest, AtThrows) {
    fixed_vector<int, 5> vec;
    vec.push_back(1);
    EXPECT_THROW(vec.at(1), std::out_of_range);
    const auto& cvec = vec;
    EXPECT_THROW(cvec.at(1), std::out_of_range);
}

TEST(FixedVectorTest, Erase) {
    fixed_vector<int, 10> vec;
    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
    }

    // erase first
    auto it = vec.erase(vec.begin());
    EXPECT_EQ(*it, 1);
    EXPECT_EQ(vec.size(), 9);
    EXPECT_EQ(vec[0], 1);
    EXPECT_EQ(vec[8], 9);

    // erase last
    it = vec.erase(vec.end() - 1);
    EXPECT_EQ(it, vec.end());
    EXPECT_EQ(vec.size(), 8);
    EXPECT_EQ(vec[7], 8);

    // erase middle
    it = vec.erase(vec.begin() + 3); // erase '4' from {1,2,3,4,5,6,7,8}
    EXPECT_EQ(*it, 5);
    EXPECT_EQ(vec.size(), 7);
    EXPECT_EQ(vec[0], 1);
    EXPECT_EQ(vec[1], 2);
    EXPECT_EQ(vec[2], 3);
    EXPECT_EQ(vec[3], 5);
}

TEST(FixedVectorTest, EraseRange) {
    fixed_vector<int, 10> vec;
    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
    }

    // erase range in the middle
    auto it = vec.erase(vec.begin() + 2, vec.begin() + 5); // erase 2, 3, 4
    EXPECT_EQ(*it, 5);
    EXPECT_EQ(vec.size(), 7);
    EXPECT_EQ(vec[0], 0);
    EXPECT_EQ(vec[1], 1);
    EXPECT_EQ(vec[2], 5);
    EXPECT_EQ(vec[3], 6);
    EXPECT_EQ(vec[6], 9);
}

TEST(FixedVectorTest, MoveOnlyType) {
    fixed_vector<std::unique_ptr<int>, 5> vec;
    vec.emplace_back(std::make_unique<int>(1));
    vec.push_back(std::make_unique<int>(2));
    EXPECT_EQ(vec.size(), 2);
    EXPECT_EQ(*vec[0], 1);
    EXPECT_EQ(*vec[1], 2);

    vec.erase(vec.begin());
    EXPECT_EQ(vec.size(), 1);
    EXPECT_EQ(*vec[0], 2);
}

struct DestructorCheck {
    static int destructor_calls;
    DestructorCheck() = default;
    ~DestructorCheck() {
        destructor_calls++;
    }
};
int DestructorCheck::destructor_calls = 0;

TEST(FixedVectorTest, DestructorCalledOnClear) {
    DestructorCheck::destructor_calls = 0;
    fixed_vector<DestructorCheck, 5> vec;
    vec.emplace_back();
    vec.emplace_back();
    vec.emplace_back();
    EXPECT_EQ(DestructorCheck::destructor_calls, 0);
    vec.clear();
    EXPECT_EQ(DestructorCheck::destructor_calls, 3);
    vec.clear();
    EXPECT_EQ(DestructorCheck::destructor_calls, 3);
}

TEST(FixedVectorTest, DestructorCalledOnErase) {
    DestructorCheck::destructor_calls = 0;
    fixed_vector<DestructorCheck, 5> vec;
    vec.emplace_back();
    vec.emplace_back();
    vec.emplace_back();
    EXPECT_EQ(DestructorCheck::destructor_calls, 0);
    vec.erase(vec.begin());
    EXPECT_EQ(DestructorCheck::destructor_calls, 1);
    vec.erase(vec.begin(), vec.end());
    EXPECT_EQ(DestructorCheck::destructor_calls, 3);
}

TEST(FixedVectorTest, DestructorCalledOnDestruction) {
    DestructorCheck::destructor_calls = 0;
    {
        fixed_vector<DestructorCheck, 5> vec;
        vec.emplace_back();
        vec.emplace_back();
        EXPECT_EQ(DestructorCheck::destructor_calls, 0);
    }
    EXPECT_EQ(DestructorCheck::destructor_calls, 2);
}

TEST(FixedVectorTest, CopyConstructor) {
    fixed_vector<int, 5> original;
    original.push_back(1);
    original.push_back(2);

    fixed_vector<int, 5> copy = original;
    EXPECT_EQ(copy.size(), 2);
    EXPECT_EQ(copy[0], 1);
    EXPECT_EQ(copy[1], 2);

    // Ensure original is untouched
    EXPECT_EQ(original.size(), 2);
    EXPECT_EQ(original[0], 1);
}

TEST(FixedVectorTest, CopyAssignment) {
    fixed_vector<int, 5> original;
    original.push_back(1);
    original.push_back(2);

    fixed_vector<int, 5> copy;
    copy.push_back(99);
    copy = original;

    EXPECT_EQ(copy.size(), 2);
    EXPECT_EQ(copy[0], 1);
    EXPECT_EQ(copy[1], 2);

    // Ensure original is untouched
    EXPECT_EQ(original.size(), 2);
}

TEST(FixedVectorTest, CopyAssignmentEdgeCases) {
    // Case 1: Destination is larger than source
    fixed_vector<DestructorCheck, 5> dest1;
    dest1.emplace_back();
    dest1.emplace_back();
    dest1.emplace_back();

    fixed_vector<DestructorCheck, 5> source1;
    source1.emplace_back();
    source1.emplace_back();

    DestructorCheck::destructor_calls = 0;
    dest1 = source1;
    EXPECT_EQ(dest1.size(), 2);
    EXPECT_EQ(DestructorCheck::destructor_calls, 1); // One surplus element should have been destroyed

    // Case 2: Destination is smaller than source
    fixed_vector<int, 5> dest2;
    dest2.push_back(1);

    fixed_vector<int, 5> source2;
    source2.push_back(10);
    source2.push_back(20);

    dest2 = source2;
    EXPECT_EQ(dest2.size(), 2);
    EXPECT_EQ(dest2[0], 10);
    EXPECT_EQ(dest2[1], 20);

    // Case 3: Sizes are equal
    fixed_vector<int, 5> dest3;
    dest3.push_back(1);
    dest3.push_back(2);

    fixed_vector<int, 5> source3;
    source3.push_back(10);
    source3.push_back(20);

    dest3 = source3;
    EXPECT_EQ(dest3.size(), 2);
    EXPECT_EQ(dest3[0], 10);
    EXPECT_EQ(dest3[1], 20);

    // Case 4: Self-assignment
    fixed_vector<int, 5> self_assign;
    self_assign.push_back(123);
    self_assign = self_assign;
    EXPECT_EQ(self_assign.size(), 1);
    EXPECT_EQ(self_assign[0], 123);
}

TEST(FixedVectorTest, MoveConstructor) {
    fixed_vector<std::string, 5> original;
    original.push_back("a");
    original.push_back("b");

    fixed_vector<std::string, 5> moved = std::move(original);
    EXPECT_EQ(moved.size(), 2);
    EXPECT_EQ(moved[0], "a");

    EXPECT_TRUE(original.empty());
}

TEST(FixedVectorTest, MoveAssignment) {
    fixed_vector<std::string, 5> original;
    original.push_back("a");
    original.push_back("b");

    fixed_vector<std::string, 5> moved;
    moved.push_back("c");
    moved = std::move(original);

    EXPECT_EQ(moved.size(), 2);
    EXPECT_EQ(moved[0], "a");

    EXPECT_TRUE(original.empty());
}

TEST(FixedVectorTest, ZeroCapacity) {
    fixed_vector<int, 0> vec;
    EXPECT_TRUE(vec.empty());
    EXPECT_EQ(vec.size(), 0);
    EXPECT_EQ(vec.capacity(), 0);
    EXPECT_THROW(vec.push_back(1), std::length_error);
}

TEST(FixedVectorTest, FrontAndBack) {
    fixed_vector<int, 3> vec;
    vec.push_back(10);
    vec.push_back(20);
    EXPECT_EQ(vec.front(), 10);
    EXPECT_EQ(vec.back(), 20);
    vec.front() = 11;
    EXPECT_EQ(vec[0], 11);

    const auto& cvec = vec;
    EXPECT_EQ(cvec.front(), 11);
    EXPECT_EQ(cvec.back(), 20);
}

TEST(FixedVectorTest, ReverseIteration) {
    fixed_vector<int, 3> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);

    int expected = 3;
    for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
        EXPECT_EQ(*it, expected--);
    }

    const auto& cvec = vec;
    expected = 3;
    for (auto it = cvec.rbegin(); it != cvec.rend(); ++it) {
        EXPECT_EQ(*it, expected--);
    }
}

TEST(FixedVectorTest, EraseEdgeCases) {
    fixed_vector<int, 5> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);
    vec.push_back(4);

    // Erase empty range
    auto it = vec.erase(vec.begin() + 1, vec.begin() + 1);
    EXPECT_EQ(vec.size(), 4);
    EXPECT_EQ(*it, 2);

    // Erase to the end
    it = vec.erase(vec.begin() + 2, vec.end());
    EXPECT_EQ(vec.size(), 2);
    EXPECT_EQ(it, vec.end());
    EXPECT_EQ(vec[0], 1);
    EXPECT_EQ(vec[1], 2);

    // Erase everything
    it = vec.erase(vec.begin(), vec.end());
    EXPECT_TRUE(vec.empty());
    EXPECT_EQ(it, vec.end());
}

TEST(FixedVectorTest, TryEmplaceBack) {
    fixed_vector<int, 3> vec;
    auto* elem1 = vec.try_emplace_back(10);
    ASSERT_NE(elem1, nullptr);
    EXPECT_EQ(*elem1, 10);
    EXPECT_EQ(vec.size(), 1);
    EXPECT_EQ(vec[0], 10);

    vec.try_emplace_back(20);
    vec.try_emplace_back(30);

    EXPECT_EQ(vec.size(), 3);

    // Vector is full
    auto* elem4 = vec.try_emplace_back(40);
    EXPECT_EQ(elem4, nullptr);
    EXPECT_EQ(vec.size(), 3);
}

TEST(FixedVectorTest, PopBack) {
    fixed_vector<int, 5> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);

    EXPECT_EQ(vec.size(), 3);
    EXPECT_EQ(vec.back(), 3);

    vec.pop_back();
    EXPECT_EQ(vec.size(), 2);
    EXPECT_EQ(vec.back(), 2);

    vec.pop_back();
    EXPECT_EQ(vec.size(), 1);
    EXPECT_EQ(vec.back(), 1);

    vec.pop_back();
    EXPECT_TRUE(vec.empty());
    EXPECT_EQ(vec.size(), 0);

    // Popping from an empty vector should be a no-op
    vec.pop_back();
    EXPECT_TRUE(vec.empty());
    EXPECT_EQ(vec.size(), 0);

    // Test with DestructorCheck
    DestructorCheck::destructor_calls = 0;
    fixed_vector<DestructorCheck, 5> vec_dc;
    vec_dc.emplace_back();
    vec_dc.emplace_back();
    vec_dc.emplace_back();
    EXPECT_EQ(DestructorCheck::destructor_calls, 0);

    vec_dc.pop_back();
    EXPECT_EQ(DestructorCheck::destructor_calls, 1);
    vec_dc.pop_back();
    EXPECT_EQ(DestructorCheck::destructor_calls, 2);
    vec_dc.pop_back();
    EXPECT_EQ(DestructorCheck::destructor_calls, 3);
    EXPECT_TRUE(vec_dc.empty());
}

TEST(FixedVectorTest, CapacityAndMaxSize) {
    fixed_vector<int, 5> vec;
    EXPECT_EQ(vec.capacity(), 5);
    EXPECT_EQ(vec.max_size(), 5);

    const fixed_vector<int, 0> zero_vec;
    EXPECT_EQ(zero_vec.capacity(), 0);
    EXPECT_EQ(zero_vec.max_size(), 0);
}

TEST(FixedVectorTest, ConstIteratorMethods) {
    fixed_vector<int, 5> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);

    const auto& cvec = vec;

    // Test cbegin() and cend()
    int expected_val = 1;
    for (auto it = cvec.cbegin(); it != cvec.cend(); ++it) {
        EXPECT_EQ(*it, expected_val++);
    }
    EXPECT_EQ(expected_val, 4); // Should have iterated 1, 2, 3

    // Test crbegin() and crend()
    expected_val = 3;
    for (auto it = cvec.crbegin(); it != cvec.crend(); ++it) {
        EXPECT_EQ(*it, expected_val--);
    }
    EXPECT_EQ(expected_val, 0); // Should have iterated 3, 2, 1

    // Test on empty vector
    fixed_vector<int, 5> empty_vec;
    const auto& cempty_vec = empty_vec;
    EXPECT_EQ(cempty_vec.cbegin(), cempty_vec.cend());
    EXPECT_EQ(cempty_vec.crbegin(), cempty_vec.crend());
}

TEST(FixedVectorTest, EraseInvalidRange) {
    fixed_vector<int, 5> vec;
    vec.push_back(1);
    vec.push_back(2);
    vec.push_back(3);
    vec.push_back(4);

    // Test with first > last, which should do nothing
    auto original_size = vec.size();
    auto it = vec.erase(vec.begin() + 2, vec.begin() + 1);

    // Verify nothing happened
    EXPECT_EQ(vec.size(), original_size);
    EXPECT_EQ(vec[0], 1);
    EXPECT_EQ(vec[1], 2);
    EXPECT_EQ(vec[2], 3);
    EXPECT_EQ(vec[3], 4);

    // The returned iterator should be the 'first' iterator passed in
    EXPECT_EQ(it, vec.begin() + 2);
    EXPECT_EQ(*it, 3);
}

TEST(FixedVectorTest, ComparisonOperators) {
    fixed_vector<int, 5> vec1;
    vec1.push_back(1);
    vec1.push_back(2);

    fixed_vector<int, 5> vec2;
    vec2.push_back(1);
    vec2.push_back(2);

    fixed_vector<int, 5> vec3;
    vec3.push_back(1);
    vec3.push_back(99);

    fixed_vector<int, 5> vec4;
    vec4.push_back(1);

    fixed_vector<int, 5> empty1;
    fixed_vector<int, 5> empty2;

    EXPECT_TRUE(vec1 == vec2);
    EXPECT_FALSE(vec1 != vec2);

    EXPECT_FALSE(vec1 == vec3);
    EXPECT_TRUE(vec1 != vec3);

    EXPECT_FALSE(vec1 == vec4);
    EXPECT_TRUE(vec1 != vec4);

    EXPECT_TRUE(empty1 == empty2);
    EXPECT_FALSE(empty1 != empty2);

    EXPECT_FALSE(vec1 == empty1);
    EXPECT_TRUE(vec1 != empty1);
}

// Verify that fixed_vector enforces nothrow move requirements at compile time.
// Types with throwing move constructors/assignments are rejected by static_assert.
struct NothrowMovable {
    NothrowMovable() = default;
    NothrowMovable(NothrowMovable&&) noexcept = default;
    NothrowMovable& operator=(NothrowMovable&&) noexcept = default;
    NothrowMovable(const NothrowMovable&) = default;
    NothrowMovable& operator=(const NothrowMovable&) = default;
};

TEST(FixedVectorTest, NothrowMoveConstraint) {
    // Verify that fixed_vector works with nothrow-movable types
    fixed_vector<NothrowMovable, 5> vec;
    vec.emplace_back();
    EXPECT_EQ(vec.size(), 1);

    // Move construction should be noexcept
    static_assert(std::is_nothrow_move_constructible_v<fixed_vector<NothrowMovable, 5>>);
    static_assert(std::is_nothrow_move_assignable_v<fixed_vector<NothrowMovable, 5>>);
    static_assert(std::is_nothrow_move_constructible_v<fixed_vector<int, 5>>);
    static_assert(std::is_nothrow_move_assignable_v<fixed_vector<int, 5>>);
    static_assert(std::is_nothrow_move_constructible_v<fixed_vector<std::string, 5>>);
    static_assert(std::is_nothrow_move_assignable_v<fixed_vector<std::string, 5>>);
}

// Types with throwing move operations — used only in compile-time rejection checks below.
struct ThrowingMoveConstructor {
    ThrowingMoveConstructor() = default;
    ThrowingMoveConstructor(ThrowingMoveConstructor&&) noexcept(false) {
    }
    ThrowingMoveConstructor& operator=(ThrowingMoveConstructor&&) noexcept = default;
};

struct ThrowingMoveAssignment {
    ThrowingMoveAssignment() = default;
    ThrowingMoveAssignment(ThrowingMoveAssignment&&) noexcept = default;
    ThrowingMoveAssignment& operator=(ThrowingMoveAssignment&&) noexcept(false) {
        return *this;
    }
};

struct ThrowingBothMoveOps {
    ThrowingBothMoveOps() = default;
    ThrowingBothMoveOps(ThrowingBothMoveOps&&) noexcept(false) {
    }
    ThrowingBothMoveOps& operator=(ThrowingBothMoveOps&&) noexcept(false) {
        return *this;
    }
};

// Verify at compile time that fixed_vector rejects types whose move operations can throw.
// The static_asserts inside fixed_vector prevent instantiation of these types.
// We verify this indirectly: if fixed_vector's constraint is working, these types must not
// satisfy the nothrow move requirements.
TEST(FixedVectorTest, ThrowingMoveTypesAreRejected) {
    // Confirm the types themselves have throwing move operations
    static_assert(!std::is_nothrow_move_constructible_v<ThrowingMoveConstructor>,
                  "ThrowingMoveConstructor should not be nothrow move constructible");
    static_assert(!std::is_nothrow_move_constructible_v<ThrowingBothMoveOps>,
                  "ThrowingBothMoveOps should not be nothrow move constructible");
    static_assert(!std::is_nothrow_move_assignable_v<ThrowingMoveAssignment>,
                  "ThrowingMoveAssignment should not be nothrow move assignable");
    static_assert(!std::is_nothrow_move_assignable_v<ThrowingBothMoveOps>,
                  "ThrowingBothMoveOps should not be nothrow move assignable");

    // fixed_vector<ThrowingMoveConstructor, 5> would fail to compile due to static_assert.
    // fixed_vector<ThrowingMoveAssignment, 5> would fail to compile due to static_assert.
    // fixed_vector<ThrowingBothMoveOps, 5> would fail to compile due to static_assert.
    //
    // These cannot be tested at runtime since instantiation itself is a compile error.
    // The static_asserts above confirm the trait checks that fixed_vector relies on.
}

TEST(FixedVectorTest, InitializerListConstructor) {
    // Basic construction
    fixed_vector<int, 5> vec1 = {1, 2, 3};
    EXPECT_EQ(vec1.size(), 3);
    EXPECT_EQ(vec1[0], 1);
    EXPECT_EQ(vec1[1], 2);
    EXPECT_EQ(vec1[2], 3);

    // Empty list
    fixed_vector<int, 5> vec2 = {};
    EXPECT_TRUE(vec2.empty());
    EXPECT_EQ(vec2.size(), 0);

    // Full capacity
    fixed_vector<int, 3> vec3 = {10, 20, 30};
    EXPECT_EQ(vec3.size(), 3);
    EXPECT_EQ(vec3[2], 30);

    // Exceeding capacity - use a lambda to avoid comma issues with the macro
    EXPECT_THROW(([] { fixed_vector<int, 2> vec4 = {1, 2, 3}; }()), std::length_error);

    // With strings
    fixed_vector<std::string, 4> vec5 = {"hello", "world"};
    EXPECT_EQ(vec5.size(), 2);
    EXPECT_EQ(vec5[0], "hello");
    EXPECT_EQ(vec5[1], "world");
}

TEST(FixedVectorTest, StdVectorConstructor) {
    // Test case 1: Construct from an empty std::vector
    std::vector<int> empty_std_vec = {};
    fixed_vector<int, 5> vec_from_empty(empty_std_vec);
    EXPECT_TRUE(vec_from_empty.empty());
    EXPECT_EQ(vec_from_empty.size(), 0);

    // Test case 2: Construct from a std::vector with elements (within capacity)
    std::vector<int> small_std_vec = {1, 2, 3};
    fixed_vector<int, 5> vec_from_small(small_std_vec);
    EXPECT_EQ(vec_from_small.size(), 3);
    EXPECT_EQ(vec_from_small[0], 1);
    EXPECT_EQ(vec_from_small[1], 2);
    EXPECT_EQ(vec_from_small[2], 3);

    // Test case 3: Construct from a std::vector with elements (exactly capacity)
    std::vector<int> full_std_vec = {10, 20, 30, 40, 50};
    fixed_vector<int, 5> vec_from_full(full_std_vec);
    EXPECT_EQ(vec_from_full.size(), 5);
    EXPECT_EQ(vec_from_full[0], 10);
    EXPECT_EQ(vec_from_full[4], 50);

    // Test case 4: Construct from a std::vector with elements exceeding capacity
    std::vector<int> large_std_vec = {1, 2, 3, 4, 5, 6};
    EXPECT_THROW(([large_std_vec] { fixed_vector<int, 5> vec_from_large(large_std_vec); }()), std::length_error);

    // Test case 5: Construct with std::string elements
    std::vector<std::string> string_std_vec = {"apple", "banana"};
    fixed_vector<std::string, 3> vec_from_strings(string_std_vec);
    EXPECT_EQ(vec_from_strings.size(), 2);
    EXPECT_EQ(vec_from_strings[0], "apple");
    EXPECT_EQ(vec_from_strings[1], "banana");
}
