#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <thread>
#include "renderive/renderable/Renderable.hpp"
#include "renderive/scene/Scene.hpp"
namespace {
template <class Predicate>
bool wait_until(Predicate&& predicate) {
    const auto deadline = std::chrono::steady_clock::now() +
                          std::chrono::seconds(2);
    while (!predicate()) {
        if (std::chrono::steady_clock::now() >= deadline)
            return false;
        std::this_thread::yield();
    }
    return true;
}
}
struct Scene_Base_Test_Renderable : Renderable_Base {
    explicit Scene_Base_Test_Renderable(Scene_Base& scene) : Renderable_Base(scene) {}
    void prepare(const Prepare_Render_Context& context) override {
        render_sequence = context.frame.render_sequence;
        ++render_count;
    }
    std::uint64_t render_sequence{};
    int render_count{};
};
TEST(scene_base_test, publishes_renderable_collection_before_background_render) {
    Scene2D_Context<> scene;
    auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
    scene.attach_renderable(renderable);
    scene.render();
    scene.wait_for_render();
    EXPECT_EQ(renderable->render_count, 1);
    EXPECT_EQ(renderable->render_sequence, 1);
    EXPECT_EQ(scene.renderable_count(), 1);
}
struct Scene_Base_Throwing_Renderable : Renderable_Base {
    explicit Scene_Base_Throwing_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
    void prepare(const Prepare_Render_Context&) override {
        throw std::runtime_error("render failed");
    }
};
TEST(scene_base_test, wait_for_render_propagates_background_render_failure) {
    Scene2D_Context<> scene;
    auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
    scene.attach_renderable(renderable);
    scene.render();
    EXPECT_THROW(scene.wait_for_render(), std::runtime_error);
}
TEST(scene_base_test, scene_destruction_does_not_throw_after_background_render_failure) {
    {
        Scene2D_Context<> scene;
        auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
        scene.attach_renderable(renderable);
        scene.render();
    }
    EXPECT_TRUE(true);
}
struct Scene_Base_Blocking_Renderable : Renderable_Base {
    explicit Scene_Base_Blocking_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
    void prepare(const Prepare_Render_Context&) override {
        std::unique_lock lock(mutex);
        rendering = true;
        condition.notify_all();
        condition.wait(lock, [this] {
            return released;
        });
    }
    void wait_rendering() {
        std::unique_lock lock(mutex);
        condition.wait(lock, [this] {
            return rendering;
        });
    }
    void release() {
        std::lock_guard lock(mutex);
        released = true;
        condition.notify_all();
    }
    std::mutex mutex;
    std::condition_variable condition;
    bool rendering{};
    bool released{};
};
TEST(scene_base_test, scene_model_mutation_does_not_wait_for_background_render) {
    Scene2D_Context<> scene;
    auto renderable = std::make_shared<Scene_Base_Blocking_Renderable>(scene);
    scene.attach_renderable(renderable);
    scene.render();
    renderable->wait_rendering();
    std::atomic<bool> mutation_finished{};
    std::thread mutation([&] {
        scene.set_renderable_configuration(renderable, {.cache_enabled = true});
        mutation_finished.store(true, std::memory_order_release);
    });
    std::this_thread::yield();
    EXPECT_TRUE(wait_until([&] {
        return mutation_finished.load(std::memory_order_acquire);
    }));
    renderable->release();
    mutation.join();
    EXPECT_TRUE(mutation_finished.load(std::memory_order_acquire));
    EXPECT_TRUE(renderable->configuration().cache_enabled);
}
TEST(scene_base_test, concurrent_render_and_topology_updates_remain_serialized) {
    Scene2D_Context<> scene;
    auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
    auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
    scene.attach_renderable(parent);
    scene.attach_renderable(child);
    std::thread renderer([&] {
        for (int index = 0; index < 100; ++index) {
            scene.render();
            scene.wait_for_render();
        }
    });
    std::thread mutator([&] {
        for (int index = 0; index < 100; ++index) {
            if (index % 2 == 0) {
                scene.set_dependency_parent(child, parent);
                scene.set_display_parent(child, parent);
            } else {
                scene.clear_dependency_parent(child);
                scene.clear_display_parent(child);
            }
            scene.set_renderable_configuration(child, {.cache_enabled = index % 2 == 0});
        }
    });
    renderer.join();
    mutator.join();
    EXPECT_EQ(scene.renderable_count(), 2);
}
TEST(scene_base_test, detach_releases_renderable_from_the_live_model) {
    Scene2D_Context<> scene;
    auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
    std::weak_ptr<Scene_Base_Test_Renderable> weak = renderable;
    scene.attach_renderable(renderable);
    scene.render();
    scene.wait_for_render();
    scene.detach_renderable(renderable).wait();
    renderable.reset();
    EXPECT_TRUE(weak.expired());
    EXPECT_EQ(scene.renderable_count(), 0);
}
TEST(scene_base_test, attach_rejects_null_renderable) {
    Scene2D_Context<> scene;
    EXPECT_THROW(scene.attach_renderable({}), std::invalid_argument);
}
TEST(scene_base_test, topology_rejects_unattached_renderables) {
    Scene2D_Context<> scene;
    auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
    auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
    scene.attach_renderable(child);
    EXPECT_THROW(scene.set_dependency_parent(child, parent).wait(), std::invalid_argument);
    EXPECT_THROW(scene.set_display_parent(child, parent).wait(), std::invalid_argument);
    scene.attach_renderable(parent);
    auto detached_child = std::make_shared<Scene_Base_Test_Renderable>(scene);
    EXPECT_THROW(scene.set_dependency_parent(detached_child, parent).wait(), std::invalid_argument);
}
TEST(scene_base_test, setting_same_topology_parent_is_a_noop) {
    Scene2D_Context<> scene;
    auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
    auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
    scene.attach_renderable(parent);
    scene.attach_renderable(child);
    scene.set_dependency_parent(child, parent).wait();
    const auto revision = child->prepare_revision();
    EXPECT_NO_THROW(scene.set_dependency_parent(child, parent).wait());
    EXPECT_EQ(child->prepare_revision(), revision);
    const auto topology = scene.topology_snapshot();
    EXPECT_EQ(topology.dependency.size(), 2);
    bool found{};
    for (const auto& relation : topology.dependency) {
        if (relation.child.get() == child.get()) {
            EXPECT_EQ(relation.parent.get(), parent.get());
            found = true;
        }
    }
    EXPECT_TRUE(found);
}
TEST(scene_base_test, display_graph_rejects_cycles) {
    Scene2D_Context<> scene;
    auto first = std::make_shared<Scene_Base_Test_Renderable>(scene);
    auto second = std::make_shared<Scene_Base_Test_Renderable>(scene);
    auto third = std::make_shared<Scene_Base_Test_Renderable>(scene);
    scene.attach_renderable(first);
    scene.attach_renderable(second);
    scene.attach_renderable(third);
    scene.set_display_parent(second, first);
    scene.add_display_parent(third, second);
    EXPECT_THROW(scene.add_display_parent(first, third).wait(), std::invalid_argument);
}
TEST(scene_base_test, mutation_callback_receives_apply_failure) {
    Scene2D_Context<> scene;
    auto first = std::make_shared<Scene_Base_Test_Renderable>(scene);
    auto second = std::make_shared<Scene_Base_Test_Renderable>(scene);
    scene.attach_renderable(first).wait();
    scene.attach_renderable(second).wait();
    scene.set_display_parent(second, first).wait();
    std::atomic<bool> callback_called{};
    std::atomic<bool> callback_failed{};
    auto mutation = scene.set_display_parent(first, second, [&](std::exception_ptr exception) {
        callback_failed.store(static_cast<bool>(exception), std::memory_order_release);
        callback_called.store(true, std::memory_order_release);
    });
    EXPECT_THROW(mutation.wait(), std::invalid_argument);
    EXPECT_TRUE(wait_until([&] { return callback_called.load(std::memory_order_acquire); }));
    EXPECT_TRUE(callback_failed.load(std::memory_order_acquire));
}
TEST(scene_base_test, atomic_attach_can_place_existing_display_children) {
    Scene2D_Context<> scene;
    auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
    auto axis = std::make_shared<Scene_Base_Test_Renderable>(scene);
    auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
    scene.attach_renderable(parent);
    scene.attach_renderable(axis);
    scene.attach_renderable(renderable, {
        .display_parents = {parent},
        .display_children = {axis},
        .dependency_parents = {parent, axis}
    });
    scene.wait_for_mutations();
    const auto topology = scene.topology_snapshot();
    const auto contains = [](const auto& relationships, const auto& child,
                             const auto& parent_value) {
        return std::ranges::any_of(relationships, [&](const auto& relationship) {
            return relationship.child.get() == child.get() &&
                   relationship.parent.get() == parent_value.get();
        });
    };
    EXPECT_TRUE(contains(topology.display, renderable, parent));
    EXPECT_TRUE(contains(topology.display, axis, renderable));
    EXPECT_TRUE(contains(topology.dependency, renderable, parent));
    EXPECT_TRUE(contains(topology.dependency, renderable, axis));
}
TEST(scene_base_test, failed_atomic_attach_rolls_back_every_topology) {
    Scene2D_Context<> scene;
    auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
    auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
    scene.attach_renderable(parent);

    EXPECT_THROW(scene.attach_renderable(renderable, {
        .display_parents = {parent},
        .display_children = {parent},
        .dependency_parents = {parent}
    }).wait(), std::invalid_argument);
    EXPECT_EQ(scene.renderable_count(), 1u);
    EXPECT_NO_THROW(scene.attach_renderable(renderable, {
        .display_parents = {parent},
        .dependency_parents = {parent}
    }).wait());
    EXPECT_EQ(scene.renderable_count(), 2u);
}
TEST(scene_base_test, topology_snapshot_retains_renderable_lifetime) {
    Scene2D_Context<> scene;
    auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
    std::weak_ptr<Scene_Base_Test_Renderable> weak = renderable;
    scene.attach_renderable(renderable).wait();
    const auto snapshot = scene.topology_snapshot();
    scene.detach_renderable(renderable).wait();
    renderable.reset();
    EXPECT_FALSE(weak.expired());
    EXPECT_EQ(snapshot.renderables.size(), 1);
    EXPECT_EQ(snapshot.renderables.front().get(), weak.lock().get());
}
TEST(scene_base_test, topology_snapshot_is_safe_during_topology_updates) {
    Scene2D_Context<> scene;
    auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
    auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
    scene.attach_renderable(parent);
    scene.attach_renderable(child);
    scene.wait_for_mutations();
    std::thread writer([&] {
        for (int index = 0; index < 500; ++index) {
            if (index % 2 == 0) {
                scene.set_dependency_parent(child, parent);
                scene.set_display_parent(child, parent);
            } else {
                scene.clear_dependency_parent(child);
                scene.clear_display_parent(child);
            }
        }
    });
    for (int index = 0; index < 500; ++index) {
        const auto snapshot = scene.topology_snapshot();
        EXPECT_EQ(snapshot.renderables.size(), 2);
        EXPECT_EQ(snapshot.display.size(), 2);
        EXPECT_EQ(snapshot.dependency.size(), 2);
    }
    writer.join();
}
TEST(scene_base_test, all_waiters_receive_the_same_render_failure) {
    Scene2D_Context<> scene;
    auto renderable = std::make_shared<Scene_Base_Throwing_Renderable>(scene);
    scene.attach_renderable(renderable);
    scene.render();
    std::atomic<int> failures{};
    auto waiter = [&] {
        try {
            scene.wait_for_render();
        } catch (const std::runtime_error&) {
            failures.fetch_add(1, std::memory_order_relaxed);
        }
    };
    std::thread first(waiter);
    std::thread second(waiter);
    first.join();
    second.join();
    EXPECT_EQ(failures.load(), 2);
}
struct Scene_Base_Throw_Once_Renderable : Renderable_Base {
    explicit Scene_Base_Throw_Once_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
    void prepare(const Prepare_Render_Context&) override {
        if (render_count.fetch_add(1, std::memory_order_acq_rel) == 0) {
            throw std::runtime_error("first render failed");
        }
    }
    std::atomic<int> render_count{};
};
TEST(scene_base_test, unobserved_render_failure_survives_next_successful_submission) {
    Scene2D_Context<> scene;
    auto renderable = std::make_shared<Scene_Base_Throw_Once_Renderable>(scene);
    scene.attach_renderable(renderable);
    scene.render();
    scene.render();
    EXPECT_THROW(scene.wait_for_render(), std::runtime_error);
    EXPECT_EQ(renderable->render_count.load(std::memory_order_acquire), 2);
    scene.render();
    EXPECT_NO_THROW(scene.wait_for_render());
    EXPECT_EQ(renderable->render_count.load(std::memory_order_acquire), 3);
}
struct Scene_Base_Render_Execution_Renderable : Renderable_Base {
    explicit Scene_Base_Render_Execution_Renderable(Scene_Base& scene) : Renderable_Base(scene, {.cache_enabled = false}) {}
    void prepare(const Prepare_Render_Context&) override {
        scene().wait_for_render();
        wait_returned.store(true, std::memory_order_release);
        try {
            scene().render();
        } catch (const std::logic_error&) {
            nested_render_rejected.store(true, std::memory_order_release);
        }
        try {
            scene().set_renderable_configuration(shared_from_this(),
                                                 {.cache_enabled = true});
            mutation_completed.store(true, std::memory_order_release);
        } catch (...) {
        }
    }
    std::atomic<bool> wait_returned{};
    std::atomic<bool> nested_render_rejected{};
    std::atomic<bool> mutation_completed{};
};

TEST(scene_base_test, attach_builder_is_synchronous_before_runtime) {
    Scene2D_Context<> scene;
    auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
    {
        auto builder = scene.attach_builder();
        builder.attach_renderable(renderable);
        EXPECT_EQ(scene.renderable_count(), 1u);
    }
    scene.render();
    scene.wait_for_render();
    EXPECT_EQ(renderable->render_count, 1);
}
TEST(scene_base_test, attach_builder_is_rejected_after_runtime_starts) {
    Scene2D_Context<> scene;
    auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
    scene.attach_renderable(renderable).wait();
    EXPECT_THROW(scene.attach_builder(), std::logic_error);
}
TEST(scene_base_test, taskflow_execution_can_mutate_live_model_but_cannot_submit_nested_render) {
    Scene2D_Context<> scene;
    auto renderable = std::make_shared<Scene_Base_Render_Execution_Renderable>(scene);
    scene.attach_renderable(renderable);
    scene.render();
    scene.wait_for_render();
    EXPECT_TRUE(renderable->wait_returned.load(std::memory_order_acquire));
    EXPECT_TRUE(renderable->nested_render_rejected.load(std::memory_order_acquire));
    EXPECT_TRUE(renderable->mutation_completed.load(std::memory_order_acquire));
}
