/*
 * Copyright (c) Contributors to the Open 3D Engine Project.
 * For complete copyright and license terms please see the LICENSE at the root of this distribution.
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 *
 */

#include <Asset/EditorAssetSystemComponent.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/IOUtils.h>
#include <AzCore/UnitTest/UnitTest.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <Editor/Framework/ScriptCanvasGraphUtilities.h>
#include <Editor/Framework/ScriptCanvasTraceUtilities.h>
#include <Framework/ScriptCanvasTestNodes.h>
#include <Framework/ScriptCanvasTestUtilities.h>
#include <ScriptCanvas/Asset/RuntimeAsset.h>
#include <ScriptCanvas/Asset/RuntimeAssetHandler.h>
#include <ScriptCanvas/Execution/RuntimeComponent.h>

namespace ScriptCanvasTestUtilitiesCPP
{
    const char* k_defaultExtension = "scriptcanvas";
    const char* k_scriptEventExtension = "scriptevents";
    const char* k_unitTestDirPathRelative = "@engroot@/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests";
}

namespace ScriptCanvasTests
{
    using namespace ScriptCanvas;

    void ExpectParse(AZStd::string_view graphPath)
    {
        AZ_TEST_START_TRACE_SUPPRESSION;
        const AZStd::string filePath = AZStd::string::format("%s/%s.%s", ScriptCanvasTestUtilitiesCPP::k_unitTestDirPathRelative, graphPath.data(), ScriptCanvasTestUtilitiesCPP::k_defaultExtension);

        ScriptCanvasEditor::RunGraphSpec runGraphSpec;
        runGraphSpec.graphPath = filePath;
        runGraphSpec.dirPath = ScriptCanvasTestUtilitiesCPP::k_unitTestDirPathRelative;
        runGraphSpec.runSpec.processOnly = true;
        runGraphSpec.runSpec.execution = ExecutionMode::Interpreted;
        auto reporters = ScriptCanvasEditor::RunGraph(runGraphSpec);
        ScriptCanvasTests::VerifyReporter(reporters.front());
        AZ_TEST_STOP_TRACE_SUPPRESSION_NO_COUNT;
    }

    void ExpectParseError(AZStd::string_view graphPath)
    {
        AZ_TEST_START_TRACE_SUPPRESSION;
        const AZStd::string filePath = AZStd::string::format("%s/%s.%s", ScriptCanvasTestUtilitiesCPP::k_unitTestDirPathRelative, graphPath.data(), ScriptCanvasTestUtilitiesCPP::k_defaultExtension);
        ScriptCanvasEditor::RunGraphSpec runGraphSpec;
        runGraphSpec.runSpec.processOnly = true;
        runGraphSpec.graphPath = filePath;
        runGraphSpec.dirPath = ScriptCanvasTestUtilitiesCPP::k_unitTestDirPathRelative;
        runGraphSpec.runSpec.execution = ExecutionMode::Interpreted;
        auto reporters = ScriptCanvasEditor::RunGraph(runGraphSpec);
        reporters.front().MarkExpectParseError();
        ScriptCanvasTests::VerifyReporter(reporters.front());
        AZ_TEST_STOP_TRACE_SUPPRESSION_NO_COUNT;
    }

    AZStd::string_view GetGraphNameFromPath(AZStd::string_view graphPath)
    {
        graphPath.remove_prefix(AZStd::min(graphPath.find_last_of("\\/") + 1, graphPath.size()));
        return graphPath;
    }

    void VerifyReporter(const ScriptCanvasEditor::Reporter& reporter)
    {
        if (!reporter.IsGraphLoaded())
        {
            ADD_FAILURE() << "Graph was not successfully loaded.";
        }
        else if (reporter.ExpectsParseError())
        {
            if (!reporter.IsParseAttemptMade())
            {
                ADD_FAILURE() << "Expected a parse error but the graph never attempted to be parsed";
            }
            else if (reporter.IsCompiled())
            {
                ADD_FAILURE() << "Expected a parse error but graph compiled successfully";
            }
        }
        else if (!reporter.IsCompiled())
        {
            ADD_FAILURE() << "Graph failed to compile";
        }
        else if (!reporter.GetScriptCanvasId().IsValid())
        {
            ADD_FAILURE() << "Graph is not valid, wasn't assigned properly to an entity";
        }
        else if (reporter.IsReportFinished())
        {
            bool reportCheckpoints = false;

            if (!reporter.IsProcessOnly())
            {
                const auto& successes = reporter.GetSuccess();
                for (const auto& success : successes)
                {
                    SUCCEED() << success.c_str();
                }

                if (!reporter.IsActivated())
                {
                    ADD_FAILURE() << "Graph did not activate";
                }

                if (!reporter.IsDeactivated())
                {
                    ADD_FAILURE() << "Graph did not deactivate";
                    reportCheckpoints = true;
                }

                if (!reporter.ExpectsRuntimeFailure())
                {
                    if (!reporter.IsComplete())
                    {
                        ADD_FAILURE() << "Graph was not marked complete";
                        reportCheckpoints = true;
                    }

                    if (!reporter.IsErrorFree())
                    {
                        ADD_FAILURE() << "Graph execution had errors";
                        reportCheckpoints = true;

                        const auto& failures = reporter.GetFailure();
                        for (const auto& failure : failures)
                        {
                            ADD_FAILURE() << failure.c_str();
                        }
                    }
                }
                else
                {
                    if (reporter.IsErrorFree())
                    {
                        ADD_FAILURE() << "Graph expected error, but didn't report any";
                        reportCheckpoints = true;
                    }
                }
            }

            if (reportCheckpoints && !reporter.IsProcessOnly())
            {
                const auto& checkpoints = reporter.GetCheckpoints();
                if (checkpoints.empty())
                {
                    ADD_FAILURE() << "No checkpoints or other unit test nodes found, using them can help parse graph test failures";
                }
                else
                {
                    AZStd::string checkpointPath = "Checkpoint Path:\n";
                    int i = 0;

                    for (const auto& checkpoint : checkpoints)
                    {
                        checkpointPath += AZStd::string::format("%2d: %s\n", ++i, checkpoint.c_str()).c_str();
                    }

                    ADD_FAILURE() << checkpointPath.c_str();
                }
            }
            else
            {
                const Execution::PerformanceTrackingReport& performance = reporter.GetPerformanceReport();

                if (reporter.GetExecutionMode() == ExecutionMode::Interpreted)
                {
                    std::cerr << "[INTERPRETED] ";
                }
                else
                {
                    std::cerr << "[     NATIVE] ";
                }

                std::cerr << AZStd::string::format
                    (" Parse: %4.2f ms, Translate: %4.2f ms\n"
                    , reporter.GetParseDuration() / 1000.0
                    , reporter.GetTranslateDuration() / 1000.0).c_str();

                double ready = aznumeric_caster(performance.timing.initializationTime);
                double instant = aznumeric_caster(performance.timing.executionTime);
                double latent = aznumeric_caster(performance.timing.latentTime);
                double total = aznumeric_caster(performance.timing.totalTime);

                std::cerr << "[ INITIALIZE] " << AZStd::string::format("%7.3f ms \n", ready / 1000.0).c_str();

                std::cerr << "[  EXECUTION] " << AZStd::string::format("%7.3f ms \n", instant / 1000.0).c_str();

                std::cerr << "[     LATENT] " << AZStd::string::format("%7.3f ms \n", latent / 1000.0).c_str();

                std::cerr << "[      TOTAL] " << AZStd::string::format("%7.3f ms ", total / 1000.0).c_str();

                switch (reporter.GetExecutionConfiguration())
                {
                case ScriptCanvasEditor::ExecutionConfiguration::Debug:
                    std::cerr << "[  DEBUG] ";
                    break;
                case ScriptCanvasEditor::ExecutionConfiguration::Performance:
                    std::cerr << "[PERFORM] ";
                    break;
                case ScriptCanvasEditor::ExecutionConfiguration::Release:
                    std::cerr << "[RELEASE] ";
                    break;
                case ScriptCanvasEditor::ExecutionConfiguration::Traced:
                    std::cerr << "[ TRACED] ";
                    break;
                }
                std::cerr << "\n";
            }
        }
        else
        {
            ADD_FAILURE() << "Graph report did not finish";
        }
    }

    void RunUnitTestGraph(AZStd::string_view graphPath)
    {
        ScriptCanvasTests::RunUnitTestGraph(graphPath, ScriptCanvasEditor::RunSpec());
    }

    void RunUnitTestGraph(AZStd::string_view graphPath, ExecutionMode execution)
    {
        ScriptCanvasEditor::RunSpec spec;
        spec.execution = execution;
        ScriptCanvasTests::RunUnitTestGraph(graphPath, spec);
    }

    void RunUnitTestGraph(AZStd::string_view graphPath, ExecutionMode execution, const ScriptCanvasEditor::DurationSpec& duration)
    {
        ScriptCanvasEditor::RunSpec runSpec;
        runSpec.execution = execution;
        runSpec.duration = duration;
        RunUnitTestGraph(graphPath, runSpec);
    }

    void RunUnitTestGraph(AZStd::string_view graphPath, ExecutionMode execution, AZStd::string_view dependentScriptEvent)
    {
        AZ::Data::AssetType assetType(azrtti_typeid<ScriptEvents::ScriptEventsAsset>());
        if (auto scriptEventAssetHandler = AZ::Data::AssetManager::Instance().GetHandler(assetType))
        {
            const AZStd::string fullPath = AZStd::string::format("%s/%s.%s", ScriptCanvasTestUtilitiesCPP::k_unitTestDirPathRelative, dependentScriptEvent.data(), ScriptCanvasTestUtilitiesCPP::k_scriptEventExtension);

            // Preload dependent scriptevent asset for testing
            AZStd::shared_ptr<AZ::Data::AssetDataStream> assetDataStream = AZStd::make_shared<AZ::Data::AssetDataStream>();
            // Read in the data from a file to a buffer, then hand ownership of the buffer over to the assetDataStream
            {
                AZ::IO::FileIOStream stream(fullPath.c_str(), AZ::IO::OpenMode::ModeRead);
                if (!AZ::IO::RetryOpenStream(stream))
                {
                    ADD_FAILURE() << AZStd::string::format("CreateJobs for \"%s\" failed because the source file could not be opened.", fullPath.c_str()).data();
                    return;
                }
                AZStd::vector<AZ::u8> fileBuffer(stream.GetLength());
                size_t bytesRead = stream.Read(fileBuffer.size(), fileBuffer.data());
                if (bytesRead != stream.GetLength())
                {
                    ADD_FAILURE() << AZStd::string::format("CreateJobs for \"%s\" failed because the source file could not be read.", fullPath.c_str()).data();
                    return;
                }
                assetDataStream->Open(AZStd::move(fileBuffer));
            }

            AZ::Data::Asset<ScriptEvents::ScriptEventsAsset> asset;
            const AZStd::string hintPath = AZStd::string::format("scriptcanvas/unittests/%s.%s", dependentScriptEvent.data(), ScriptCanvasTestUtilitiesCPP::k_scriptEventExtension);
            asset.Create(AZ::Data::AssetId(AZ::Uuid::CreateName(hintPath.data())));

            if (scriptEventAssetHandler->LoadAssetDataFromStream(asset, assetDataStream, nullptr) != AZ::Data::AssetHandler::LoadResult::LoadComplete)
            {
                ADD_FAILURE() << AZStd::string::format("Failed to load ScriptEvent asset: %s", fullPath.data()).data();
                return;
            }
            scriptEventAssetHandler->InitAsset(asset, true, false);

            ScriptCanvasEditor::RunSpec spec;
            spec.execution = execution;
            ScriptCanvasTests::RunUnitTestGraph(graphPath, spec);
        }
        else
        {
            ADD_FAILURE() << "ScriptEvent asset handler is missing.";
        }
    }

    void RunUnitTestGraph(AZStd::string_view graphPath, const ScriptCanvasEditor::DurationSpec& duration)
    {
        ScriptCanvasEditor::RunSpec spec;
        spec.duration = duration;
        ScriptCanvasTests::RunUnitTestGraph(graphPath, ExecutionMode::Interpreted, duration);
    }

    void RunUnitTestGraph(AZStd::string_view graphPath, const ScriptCanvasEditor::RunSpec& runSpec)
    {
        const AZStd::string filePath = AZStd::string::format("%s/%s.%s", ScriptCanvasTestUtilitiesCPP::k_unitTestDirPathRelative, graphPath.data(), ScriptCanvasTestUtilitiesCPP::k_defaultExtension);

        ScriptCanvasEditor::RunGraphSpec runGraphSpec;
        runGraphSpec.graphPath = filePath;
        runGraphSpec.dirPath = ScriptCanvasTestUtilitiesCPP::k_unitTestDirPathRelative;
        runGraphSpec.runSpec = runSpec;

        AZ_TEST_START_TRACE_SUPPRESSION;

        const ScriptCanvasEditor::Reporters reporters = ScriptCanvasEditor::RunGraph(runGraphSpec);

        for (const auto& reporter : reporters)
        {
            ScriptCanvasTests::VerifyReporter(reporter);
        }

        if (reporters.size() == 2)
        {
            EXPECT_EQ(reporters.front(), reporters.back());
        }
        else if (reporters.size() > 2)
        {
            for (size_t lhs(0), rhs(1); rhs != reporters.size(); ++lhs, ++rhs)
            {
                EXPECT_EQ(reporters[lhs], reporters[rhs]);
            }
        }

        AZ_TEST_STOP_TRACE_SUPPRESSION_NO_COUNT;
    }

    void RunUnitTestGraphMixed(AZStd::string_view graphPath, const ScriptCanvasEditor::DurationSpec& duration)
    {
        AZ_TEST_START_TRACE_SUPPRESSION;

        ScriptCanvasEditor::RunGraphSpec runGraphSpec;
        runGraphSpec.graphPath = graphPath;
        runGraphSpec.dirPath = ScriptCanvasTestUtilitiesCPP::k_unitTestDirPathRelative;
        runGraphSpec.runSpec.duration = duration;

        runGraphSpec.runSpec.execution = ExecutionMode::Interpreted;
        const ScriptCanvasEditor::Reporter reporterIterpreted0 = RunGraph(runGraphSpec).front();
        runGraphSpec.runSpec.execution = ExecutionMode::Native;
        const ScriptCanvasEditor::Reporter reporterNative0 = RunGraph(runGraphSpec).front();
        runGraphSpec.runSpec.execution = ExecutionMode::Interpreted;
        const ScriptCanvasEditor::Reporter reporterIterpreted1 = RunGraph(runGraphSpec).front();
        runGraphSpec.runSpec.execution = ExecutionMode::Native;
        const ScriptCanvasEditor::Reporter reporterNative1 = RunGraph(runGraphSpec).front();

        VerifyReporter(reporterIterpreted0);
        VerifyReporter(reporterNative0);

        EXPECT_TRUE(reporterIterpreted0.IsActivated());
        EXPECT_TRUE(reporterIterpreted1.IsComplete());
        EXPECT_TRUE(reporterIterpreted0.IsErrorFree());

        EXPECT_EQ(reporterNative0, reporterIterpreted0);
        EXPECT_EQ(reporterNative0, reporterIterpreted1);
        EXPECT_EQ(reporterNative1, reporterIterpreted0);
        EXPECT_EQ(reporterNative1, reporterIterpreted1);

        EXPECT_EQ(reporterNative0, reporterNative1);
        EXPECT_EQ(reporterIterpreted0, reporterIterpreted1);
        
        AZ_TEST_STOP_TRACE_SUPPRESSION_NO_COUNT;
    }

    void RunUnitTestGraphMixed(AZStd::string_view graphPath)
    {
        RunUnitTestGraphMixed(graphPath, ScriptCanvasEditor::DurationSpec());
    }

    TestBehaviorContextObject TestBehaviorContextObject::MaxReturnByValue(TestBehaviorContextObject lhs, TestBehaviorContextObject rhs)
    {
        return lhs.GetValue() >= rhs.GetValue() ? lhs : rhs;
    }

    const TestBehaviorContextObject* TestBehaviorContextObject::MaxReturnByPointer(const TestBehaviorContextObject* lhs, const TestBehaviorContextObject* rhs)
    {
        return (lhs && rhs && lhs->GetValue() >= rhs->GetValue()) ? lhs : rhs;
    }

    const TestBehaviorContextObject& TestBehaviorContextObject::MaxReturnByReference(const TestBehaviorContextObject& lhs, const TestBehaviorContextObject& rhs)
    {
        return lhs.GetValue() >= rhs.GetValue() ? lhs : rhs;
    }

    int TestBehaviorContextObject::MaxReturnByValueInteger(int lhs, int rhs)
    {
        return lhs >= rhs ? lhs : rhs;
    }

    const int* TestBehaviorContextObject::MaxReturnByPointerInteger(const int* lhs, const int* rhs)
    {
        return (lhs && rhs && (*lhs) >= (*rhs)) ? lhs : rhs;
    }

    const int& TestBehaviorContextObject::MaxReturnByReferenceInteger(const int& lhs, const int& rhs)
    {
        return lhs >= rhs ? lhs : rhs;
    }

    static void TestBehaviorContextObjectGenericConstructor(TestBehaviorContextObject* thisPtr)
    {
        new (thisPtr) TestBehaviorContextObject();
        thisPtr->SetValue(0);
    }

    void TestBehaviorContextObject::Reflect(AZ::ReflectContext* reflectContext)
    {
        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflectContext))
        {
            serializeContext->Class<TestBehaviorContextObject>()
                ->Version(0)
                ->Field("m_value", &TestBehaviorContextObject::m_value)
                ->Field("isNormalized", &TestBehaviorContextObject::m_isNormalized)
                ;
        }

        if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflectContext))
        {
            behaviorContext->Class<TestBehaviorContextObject>("TestBehaviorContextObject")
                ->Attribute(AZ::Script::Attributes::ConstructorOverride, &TestBehaviorContextObjectGenericConstructor)
                ->Attribute(AZ::Script::Attributes::GenericConstructorOverride, &TestBehaviorContextObjectGenericConstructor)
                ->Method("In", &TestBehaviorContextObject::GetValue)
                ->Method("Out", &TestBehaviorContextObject::SetValue)
                ->Method("Normalize", &TestBehaviorContextObject::Normalize)
                ->Method("IsNormalized", &TestBehaviorContextObject::IsNormalized)
                ->Method("Denormalize", &TestBehaviorContextObject::Denormalize)
                ->Method("MaxReturnByValue", &TestBehaviorContextObject::MaxReturnByValue)
                ->Method("MaxReturnByPointer", &TestBehaviorContextObject::MaxReturnByPointer)
                ->Method("MaxReturnByReference", &TestBehaviorContextObject::MaxReturnByReference)
                ->Method("MaxReturnByValueInteger", &TestBehaviorContextObject::MaxReturnByValueInteger)
                ->Method("MaxReturnByPointerInteger", &TestBehaviorContextObject::MaxReturnByPointerInteger)
                ->Method("MaxReturnByReferenceInteger", &TestBehaviorContextObject::MaxReturnByReferenceInteger)
                ->Method<bool (TestBehaviorContextObject::*)(const TestBehaviorContextObject&) const>("LessThan", &TestBehaviorContextObject::operator<)
                ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::LessThan)
                ->Method<bool (TestBehaviorContextObject::*)(const TestBehaviorContextObject&) const>("LessEqualThan", &TestBehaviorContextObject::operator<=)
                ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::LessEqualThan)
                ->Method<bool (TestBehaviorContextObject::*)(const TestBehaviorContextObject&) const>("Equal", &TestBehaviorContextObject::operator==)
                ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal)
                ;
        }
    }

    TestBehaviorContextObject MaxReturnByValue(TestBehaviorContextObject lhs, TestBehaviorContextObject rhs)
    {
        return lhs.GetValue() >= rhs.GetValue() ? lhs : rhs;
    }

    const TestBehaviorContextObject* MaxReturnByPointer(const TestBehaviorContextObject* lhs, const TestBehaviorContextObject* rhs)
    {
        return (lhs && rhs && (lhs->GetValue() >= rhs->GetValue())) ? lhs : rhs;
    }

    const TestBehaviorContextObject& MaxReturnByReference(const TestBehaviorContextObject& lhs, const TestBehaviorContextObject& rhs)
    {
        return lhs.GetValue() >= rhs.GetValue() ? lhs : rhs;
    }

    AZ::u32 TestBehaviorContextObject::s_createdCount = 0;
    AZ::u32 TestBehaviorContextObject::s_destroyedCount = 0;

    AZ::EntityId CreateClassFunctionNode(const ScriptCanvasId& scriptCanvasId, AZStd::string_view className, AZStd::string_view methodName)
    {
        using namespace ScriptCanvas;

        ScriptCanvas::NamespacePath emptyNamespaces;

        AZ::Entity* splitEntity{ aznew AZ::Entity };
        splitEntity->Init();
        AZ::EntityId methodNodeID{ splitEntity->GetId() };
        SystemRequestBus::Broadcast(&SystemRequests::CreateNodeOnEntity, methodNodeID, scriptCanvasId, ScriptCanvas::Nodes::Core::Method::RTTI_Type());
        ScriptCanvas::Nodes::Core::Method* methodNode(nullptr);
        SystemRequestBus::BroadcastResult(methodNode, &SystemRequests::GetNode<ScriptCanvas::Nodes::Core::Method>, methodNodeID);
        EXPECT_TRUE(methodNode != nullptr);
        methodNode->InitializeBehaviorMethod(emptyNamespaces, className, methodName, ScriptCanvas::PropertyStatus::None);
        return methodNodeID;
    }

    AZStd::string SlotDescriptorToString(ScriptCanvas::SlotDescriptor descriptor)
    {
        AZStd::string name;

        switch (descriptor.m_slotType)
        {
        case SlotTypeDescriptor::Data:
            name.append("Data");
            break;
        case SlotTypeDescriptor::Execution:
            name.append("Execution");
            break;
        default:
            break;
        }

        switch (descriptor.m_connectionType)
        {
        case ConnectionType::Input:
            name.append("In");
            break;
        case ConnectionType::Output:
            name.append("Out");
            break;
        default:
            break;
        }

        return name;
    }

    void DumpSlots([[maybe_unused]] const ScriptCanvas::Node& node)
    {
#if defined(AZ_ENABLE_TRACING)
        const auto& nodeslots = node.GetSlots();

        for (const auto& nodeslot : nodeslots)
        {
            AZ_TracePrintf("ScriptCanvasTest", "'%s':%s\n", nodeslot.GetName().data(), SlotDescriptorToString(nodeslot.GetDescriptor()).c_str());
        }
#endif
    }

    bool Connect(ScriptCanvas::Graph& graph, const AZ::EntityId& fromNodeID, const char* fromSlotName, const AZ::EntityId& toNodeID, const char* toSlotName, bool dumpSlotsOnFailure /*= true*/)
    {
        using namespace ScriptCanvas;
        AZ::Entity* fromNode{};
        AZ::ComponentApplicationBus::BroadcastResult(fromNode, &AZ::ComponentApplicationBus::Events::FindEntity, fromNodeID);
        AZ::Entity* toNode{};
        AZ::ComponentApplicationBus::BroadcastResult(toNode, &AZ::ComponentApplicationBus::Events::FindEntity, toNodeID);
        if (fromNode && toNode)
        {
            Node* from = AZ::EntityUtils::FindFirstDerivedComponent<Node>(fromNode);
            Node* to = AZ::EntityUtils::FindFirstDerivedComponent<Node>(toNode);

            auto fromSlotId = from->GetSlotId(fromSlotName);
            auto toSlotId = to->GetSlotId(toSlotName);

            if (graph.Connect(fromNodeID, fromSlotId, toNodeID, toSlotId))
            {
                return true;
            }
            else if (dumpSlotsOnFailure)
            {
                AZ_TracePrintf("ScriptCanvasTest", "Slots from:\n");
                DumpSlots(*from);
                AZ_TracePrintf("ScriptCanvasTest", "\nSlots to:\n");
                DumpSlots(*to);
            }
        }

        return false;
    }

    AZ::Entity* UnitTestEntityContext::CreateEntity(const char* name)
    {
        auto entity = aznew AZ::Entity(name);
        AddEntity(entity);
        return entity;
    }

    void UnitTestEntityContext::AddEntity(AZ::Entity* entity)
    {
        AddEntity(entity->GetId());
    }

    void UnitTestEntityContext::AddEntity(AZ::EntityId entityId)
    {
        AZ_Assert(!AzFramework::EntityIdContextQueryBus::FindFirstHandler(entityId), "Entity already belongs to a context.");
        m_unitTestEntityIdMap.emplace(entityId, entityId);
        AzFramework::EntityIdContextQueryBus::MultiHandler::BusConnect(entityId);
    }

    void UnitTestEntityContext::RemoveEntity(AZ::EntityId entityId)
    {
        auto foundIt = m_unitTestEntityIdMap.find(entityId);
        if (foundIt != m_unitTestEntityIdMap.end())
        {
            AzFramework::EntityIdContextQueryBus::MultiHandler::BusDisconnect(entityId);
            m_unitTestEntityIdMap.erase(foundIt);
        }
    }

    void UnitTestEntityContext::ActivateEntity(AZ::EntityId entityId)
    {
        if (IsOwnedByThisContext(entityId))
        {
            AZ::Entity* entity{};
            AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
            if (entity)
            {
                if (entity->GetState() == AZ::Entity::State::Constructed)
                {
                    entity->Init();
                }
                if (entity->GetState() == AZ::Entity::State::Init)
                {
                    entity->Activate();
                }
            }
        }
    }

    void UnitTestEntityContext::DeactivateEntity(AZ::EntityId entityId)
    {
        if (IsOwnedByThisContext(entityId))
        {
            AZ::Entity* entity{};
            AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
            if (entity)
            {
                if (entity->GetState() == AZ::Entity::State::Active)
                {
                    entity->Deactivate();
                }
                else if (entity->GetState() == AZ::Entity::State::Activating)
                {
                    // Queue Deactivation to next frame
                    AZ::SystemTickBus::QueueFunction(&AZ::Entity::Activate, entity);
                }
            }
        }
    }

    bool UnitTestEntityContext::DestroyEntity(AZ::Entity* entity)
    {
        if (entity)
        {
            auto foundIt = m_unitTestEntityIdMap.find(entity->GetId());
            if (foundIt != m_unitTestEntityIdMap.end())
            {
                AzFramework::EntityIdContextQueryBus::MultiHandler::BusDisconnect(entity->GetId());
                m_unitTestEntityIdMap.erase(foundIt);
                delete entity;
                return true;
            }
        }

        return false;
    }

    bool UnitTestEntityContext::DestroyEntityById(AZ::EntityId entityId)
    {
        AZ::Entity* entity{};
        AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
        return DestroyEntity(entity);
    }

    void UnitTestEntityContext::ResetContext()
    {
        AzFramework::EntityIdContextQueryBus::MultiHandler::BusDisconnect();
        m_unitTestEntityIdMap.clear();
    }

    AZ::EntityId UnitTestEntityContext::FindLoadedEntityIdMapping(const AZ::EntityId& staticId) const
    {
        auto idIter = m_unitTestEntityIdMap.find(staticId);

        if (idIter != m_unitTestEntityIdMap.end())
        {
            return idIter->second;
        }

        return AZ::EntityId();
    }

    AZ::Entity* UnitTestEntityContext::CloneEntity(const AZ::Entity& sourceEntity)
    {
        if (!IsOwnedByThisContext(sourceEntity.GetId()))
        {
            AZ_Warning("Script Canvas", false, "Entity %s does not belong to the unit test entity context.", sourceEntity.GetName().data());
            return {};
        }

        AZ::SerializeContext* serializeContext{};
        AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
        AZ::Entity* cloneEntity = serializeContext->CloneObject(&sourceEntity);
        if (cloneEntity)
        {
            cloneEntity->SetId(AZ::Entity::MakeId());
            AddEntity(cloneEntity);
        }

        return cloneEntity;
    }

    //AZ::Data::AssetId UnitTestEntityContext::CurrentlyInstantiatingSlice()
    //{
    //    return AZ::Data::AssetId();
    //}

    //bool UnitTestEntityContext::HandleRootEntityReloadedFromStream(AZ::Entity*, bool, AZ::SliceComponent::EntityIdToEntityIdMap*)
    //{
    //    return true;
    //}

    //AZ::SliceComponent* UnitTestEntityContext::GetRootSlice()
    //{
    //    return {};
    //}

    //const AZStd::unordered_map<AZ::EntityId, AZ::EntityId>& UnitTestEntityContext::GetLoadedEntityIdMap()
    //{
    //    return m_unitTestEntityIdMap;
    //}

    //AzFramework::SliceInstantiationTicket UnitTestEntityContext::InstantiateSlice(const AZ::Data::Asset<AZ::Data::AssetData>&,
    //    const AZ::IdUtils::Remapper<AZ::EntityId>::IdMapper&, const AZ::Data::AssetFilterCB&)
    //{
    //    return AzFramework::SliceInstantiationTicket();
    //}

    //AZ::SliceComponent::SliceInstanceAddress UnitTestEntityContext::CloneSliceInstance(
    //    AZ::SliceComponent::SliceInstanceAddress, AZ::SliceComponent::EntityIdToEntityIdMap&)
    //{
    //    return AZ::SliceComponent::SliceInstanceAddress();
    //}

    //void UnitTestEntityContext::CancelSliceInstantiation(const AzFramework::SliceInstantiationTicket&)
    //{
    //}

    //AzFramework::SliceInstantiationTicket UnitTestEntityContext::GenerateSliceInstantiationTicket()
    //{
    //    return AzFramework::SliceInstantiationTicket();
    //}

    //void UnitTestEntityContext::SetIsDynamic(bool)
    //{
    //}

    //const AzFramework::RootSliceAsset& UnitTestEntityContext::GetRootAsset() const
    //{
    //    return m_unitTestRootAsset;
    //}

} // ScriptCanvasTests
