#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <memory>
#include <thread>
#include <vector>
#include "renderive/capture/Capture.hpp"
#include "renderive/render_graph/Frame_Analysis.hpp"
#include "renderive/render_graph/Render_Plan.hpp"
#include "renderive/scene/base/Abstract_Frame.hpp"

namespace {

template <class Node>
concept Exposes_Execution_Function = requires(Node node) {
    node.function;
};

static_assert(!Exposes_Execution_Function<Render_Node>);

template <class Frame>
concept Exposes_Execution_Slot = requires(Frame& frame) {
    frame.execution_slot(0);
};

static_assert(!Exposes_Execution_Slot<Abstract_Frame>);

Render_Graph make_parallel_graph() {
    Render_Graph_Builder builder;
    const auto a = builder.emplace(100, 10, "A Prepare", Render_Node_Kind::prepare);
    const auto b = builder.emplace(101, 11, "B Prepare", Render_Node_Kind::prepare);
    const auto c = builder.emplace(102, 12, "C Paint", Render_Node_Kind::paint);
    builder.precede(a, c);
    builder.precede(b, c);
    return std::move(builder).finish();
}

}

TEST(render_dag_test, one_graph_assigns_dense_execution_slots_and_versions_only_topology) {
    Render_Plan_History history;
    const auto first = history.publish(make_parallel_graph());
    ASSERT_TRUE(first);
    ASSERT_EQ(first->graph.nodes.size(), 3u);
    EXPECT_EQ(first->graph.nodes[0].execution_index, 0u);
    EXPECT_EQ(first->graph.nodes[1].execution_index, 1u);
    EXPECT_EQ(first->graph.nodes[2].execution_index, 2u);

    const auto same = history.publish(make_parallel_graph());
    EXPECT_EQ(same->version, first->version);

    auto changed_graph = make_parallel_graph();
    changed_graph.edges.push_back({100, 101});
    const auto changed = history.publish(std::move(changed_graph));
    EXPECT_GT(changed->version, first->version);
    EXPECT_EQ(history.find(first->version), first);
    EXPECT_EQ(history.find(changed->version), changed);
}

TEST(render_dag_test, analysis_derives_wait_critical_path_and_parallel_overlap_from_frame_slots) {
    Render_Plan_History history;
    const auto plan = history.publish(make_parallel_graph());
    Frame_Snapshot snapshot;
    snapshot.frame_id = 1;
    snapshot.render_plan_version = plan->version;
    snapshot.render_start_ns = 100;
    snapshot.render_end_ns = 200;
    snapshot.node_executions.resize(plan->graph.nodes.size());
    auto complete = [&snapshot, &plan](std::size_t slot, std::uint64_t start,
                                      std::uint64_t end, std::uint32_t worker) {
        auto* execution = &snapshot.node_executions.at(slot);
        execution->node_id = plan->graph.nodes.at(slot).node_id;
        execution->start_time_ns = start;
        execution->end_time_ns = end;
        execution->worker_id = worker;
        execution->status = Node_Execution_Status::complete;
    };
    complete(0, 110, 150, 0);
    complete(1, 115, 145, 1);
    complete(2, 160, 190, 1);
    const auto analysis = analyze_frame(*plan, snapshot);

    ASSERT_EQ(analysis.nodes.size(), 3u);
    EXPECT_EQ(analysis.nodes[2].dependency_ready_time_ns, 150u);
    EXPECT_EQ(analysis.nodes[2].scheduler_wait_ns, 10u);
    EXPECT_EQ(analysis.total_render_duration_ns, 100u);
    EXPECT_EQ(analysis.total_work_duration_ns, 100u);
    EXPECT_EQ(analysis.peak_parallelism, 2u);
    EXPECT_DOUBLE_EQ(analysis.average_parallelism, 1.0);
    EXPECT_EQ(analysis.parallel_overlap_ns, 30u);
    EXPECT_EQ(analysis.critical_path, (std::vector<Render_Node_Id>{100, 102}));
    EXPECT_EQ(analysis.critical_path_duration_ns, 70u);
    EXPECT_DOUBLE_EQ(analysis.nodes[0].work_contribution, 0.4);
    EXPECT_DOUBLE_EQ(analysis.nodes[0].critical_path_contribution, 40.0 / 70.0);
}

TEST(render_dag_test, capture_controller_and_repository_capture_exact_requested_frames) {
    Capture_Controller controller;
    Capture_Repository repository;
    const auto session_id = controller.capture_frames(2);
    repository.begin_session(session_id, 2);
    const auto first = controller.begin_frame();
    const auto second = controller.begin_frame();
    const auto third = controller.begin_frame();
    EXPECT_TRUE(first.capture);
    EXPECT_TRUE(second.capture);
    EXPECT_FALSE(third.capture);
    EXPECT_EQ(first.session_id, session_id);
    EXPECT_EQ(second.session_id, session_id);
    controller.finish_frame(first, true);
    controller.finish_frame(second, true);
    EXPECT_FALSE(controller.state().enabled());

    Render_Plan_History history;
    const auto plan = history.publish(make_parallel_graph());
    for (std::uint64_t frame_id : {1u, 2u}) {
        auto snapshot = std::make_shared<Frame_Snapshot>();
        snapshot->frame_id = frame_id;
        snapshot->render_plan_version = plan->version;
        snapshot->render_start_ns = frame_id * 100;
        snapshot->render_end_ns = frame_id * 100 + 50;
        snapshot->node_executions.resize(plan->graph.nodes.size());
        for (std::size_t slot = 0; slot < plan->graph.nodes.size(); ++slot) {
            auto* execution = &snapshot->node_executions.at(slot);
            execution->node_id = plan->graph.nodes.at(slot).node_id;
            execution->start_time_ns = frame_id * 100 + slot * 10;
            execution->end_time_ns = execution->start_time_ns + 5;
            execution->worker_id = static_cast<std::uint32_t>(slot);
            execution->status = Node_Execution_Status::complete;
        }
        repository.publish(session_id, snapshot, analyze_frame(*plan, *snapshot));
    }

    const auto session = repository.session(session_id);
    ASSERT_TRUE(session);
    EXPECT_EQ(session->requested_count, 2u);
    EXPECT_EQ(session->captured_count(), 2u);
    EXPECT_FALSE(session->active());
    EXPECT_EQ(session->frames[0].snapshot->frame_id, 1u);
    EXPECT_EQ(session->frames[1].snapshot->frame_id, 2u);
}

TEST(render_dag_test, failed_capture_ticket_is_retried_until_the_requested_frame_completes) {
    Capture_Controller controller;
    const auto session_id = controller.capture_next_frame();
    const auto failed = controller.begin_frame();
    ASSERT_TRUE(failed.capture);
    controller.finish_frame(failed, false);
    EXPECT_TRUE(controller.state().enabled());
    EXPECT_EQ(controller.state().remaining_frame_count, 1u);

    const auto retry = controller.begin_frame();
    ASSERT_TRUE(retry.capture);
    EXPECT_EQ(retry.session_id, session_id);
    controller.finish_frame(retry, true);
    EXPECT_FALSE(controller.state().enabled());
    EXPECT_FALSE(controller.begin_frame().capture);
}

TEST(render_dag_test, concurrent_workers_reserve_exactly_one_capture_slot_each) {
    Capture_Controller controller;
    constexpr std::size_t requested = 64;
    controller.capture_frames(requested);
    std::atomic<std::size_t> captured{};
    std::vector<std::thread> workers;
    workers.reserve(256);
    for (std::size_t index = 0; index < 256; ++index) {
        workers.emplace_back([&] {
            const auto ticket = controller.begin_frame();
            if (!ticket.capture)
                return;
            captured.fetch_add(1, std::memory_order_relaxed);
            controller.finish_frame(ticket, true);
        });
    }
    for (auto& worker : workers)
        worker.join();
    EXPECT_EQ(captured.load(std::memory_order_relaxed), requested);
    EXPECT_FALSE(controller.state().enabled());
}
