using NUnit.Framework;
using System;
using System.Collections.Generic;
using UnityEngine.Rendering.RenderGraphModule;

#if UNITY_EDITOR
using UnityEditor.Rendering;
#endif

namespace UnityEngine.Rendering.Tests
{
    // TODO: Move this class to the Tests/Editor folder once the "IsolatedPackagesVerified" CI test correctly resolves all dependencies.
    // Currently, the URP package fails to locate the RenderGraphTestsCore class when it's placed under Tests/Editor,
    // unless the Core package is explicitly added to the "testables" list in the project manifest — which is not a sustainable solution.
    internal class RenderGraphTestsCore
    {
        // For RG Record/Hash/Compile testing, use m_RenderGraph
        protected RenderGraph m_RenderGraph;

        protected RenderPipelineAsset m_OldDefaultRenderPipeline;
        protected RenderPipelineAsset m_OldQualityRenderPipeline;

        // For RG Execute/Submit testing with rendering, use m_RenderGraphTestPipeline and m_RenderGraph in its recordRenderGraphBody
        protected RenderGraphTestPipelineAsset m_RenderGraphTestPipeline;
        protected RenderGraphTestGlobalSettings m_RenderGraphTestGlobalSettings;

        protected GameObject m_GameObject;

        // We need a camera to execute the render graph and a game object to attach a camera
        protected Camera m_Camera;

        // For the testing of the following RG steps: Execute and Submit (native) with camera rendering, use this custom RenderGraph render pipeline
        // through a camera render call to test the RG with a real ScriptableRenderContext
        internal class RenderGraphTestPipelineAsset : RenderPipelineAsset<RenderGraphTestPipelineInstance>
        {
            public Action<ScriptableRenderContext, Camera, CommandBuffer> recordRenderGraphBody;

            public RenderGraph renderGraph;

            public RenderTextureUVOriginStrategy renderTextureUVOriginStrategy;

            public bool invalidContextForTesting;

            protected override RenderPipeline CreatePipeline()
            {
                return new RenderGraphTestPipelineInstance(this);
            }

            // Called only once per UTR
            void OnEnable()
            {
                renderGraph = new();
            }
        }

        internal class RenderGraphTestPipelineInstance : RenderPipeline
        {
            RenderGraphTestPipelineAsset asset;

            // Having the RG at this level allows us to handle RG framework within Render() for easier testing
            RenderGraph m_RenderGraph;

            public RenderGraphTestPipelineInstance(RenderGraphTestPipelineAsset asset)
            {
                this.asset = asset;
                this.m_RenderGraph = asset.renderGraph;
            }

            protected override void Render(ScriptableRenderContext renderContext, List<Camera> cameras)
            {
                foreach (var camera in cameras)
                {
                    if (!camera.enabled)
                        continue;

                    var cmd = CommandBufferPool.Get();

                    RenderGraphParameters rgParams = new()
                    {
                        commandBuffer = cmd,
                        scriptableRenderContext = renderContext,
                        currentFrameIndex = Time.frameCount,
                        invalidContextForTesting = asset.invalidContextForTesting,
                        renderTextureUVOriginStrategy = asset.renderTextureUVOriginStrategy
                    };

                    try
                    {
                        // Necessary to reinitialize the state, since we have many tests which do not use this system but directly adding
                        // passes to the graph (to test the compilation) and destroying them without executing them with camera.Render()
                        // (e.g GraphicsPassWriteWaitOnAsyncPipe). So the state becomes RecordGraph because of the Builder.Destroy logic.
                        m_RenderGraph.RenderGraphState = RenderGraphState.Idle;

                        m_RenderGraph.BeginRecording(rgParams);

                        asset.recordRenderGraphBody?.Invoke(renderContext, camera, cmd);

                        m_RenderGraph.EndRecordingAndExecute();
                    }
                    catch (Exception e)
                    {
                        if (m_RenderGraph.ResetGraphAndLogException(e))
                            throw;
                        return;
                    }

                    if (rgParams.invalidContextForTesting == false)
                    {
                        renderContext.ExecuteCommandBuffer(cmd);
                    }

                    CommandBufferPool.Release(cmd);
                }

                renderContext.Submit();
            }
        }

        [SupportedOnRenderPipeline(typeof(RenderGraphTestPipelineAsset))]
        [System.ComponentModel.DisplayName("RenderGraphTest")]
        internal class RenderGraphTestGlobalSettings : RenderPipelineGlobalSettings<RenderGraphTestGlobalSettings, RenderGraphTestPipelineInstance>
        {
            [SerializeField] RenderPipelineGraphicsSettingsContainer m_Settings = new();

            protected override List<IRenderPipelineGraphicsSettings> settingsList => m_Settings.settingsList;
        }

        [OneTimeSetUp]
        public void Setup()
        {
            // Setting default global settings to the custom RG render pipeline type, no quality settings so we can rely on the default RP
            m_RenderGraphTestGlobalSettings = ScriptableObject.CreateInstance<RenderGraphTestGlobalSettings>();
#if UNITY_EDITOR
            EditorGraphicsSettings.SetRenderPipelineGlobalSettingsAsset<RenderGraphTestPipelineInstance>(m_RenderGraphTestGlobalSettings);
#endif
            // Saving old render pipelines to set them back after testing
            m_OldDefaultRenderPipeline = GraphicsSettings.defaultRenderPipeline;
            m_OldQualityRenderPipeline = QualitySettings.renderPipeline;

            // Setting the custom RG render pipeline
            m_RenderGraphTestPipeline = ScriptableObject.CreateInstance<RenderGraphTestPipelineAsset>();
            GraphicsSettings.defaultRenderPipeline = m_RenderGraphTestPipeline;
            QualitySettings.renderPipeline = m_RenderGraphTestPipeline;

            // Getting the RG from the custom asset pipeline
            m_RenderGraph = m_RenderGraphTestPipeline.renderGraph;
            m_RenderGraph.nativeRenderPassesEnabled = true;

            // Necessary to disable it for the Unit Tests, as the caller is not the same.
            RenderGraph.RenderGraphExceptionMessages.enableCaller = false;

            // We need a real ScriptableRenderContext and a camera to execute the Render Graph
            m_GameObject = new GameObject("testGameObject")
            {
                hideFlags = HideFlags.HideAndDontSave
            };
            m_GameObject.tag = "MainCamera";
            m_Camera = m_GameObject.AddComponent<Camera>();
        }

        [OneTimeTearDown]
        public void Cleanup()
        {
            GraphicsSettings.defaultRenderPipeline = m_OldDefaultRenderPipeline;
            m_OldDefaultRenderPipeline = null;

            QualitySettings.renderPipeline = m_OldQualityRenderPipeline;
            m_OldQualityRenderPipeline = null;

            m_RenderGraph.Cleanup();

            Object.DestroyImmediate(m_RenderGraphTestPipeline);

#if UNITY_EDITOR
            EditorGraphicsSettings.SetRenderPipelineGlobalSettingsAsset<RenderGraphTestPipelineInstance>(null);
#endif
            Object.DestroyImmediate(m_RenderGraphTestGlobalSettings);

            GameObject.DestroyImmediate(m_GameObject);
            m_GameObject = null;
            m_Camera = null;
        }

        [TearDown]
        public void CleanupRenderGraph()
        {
            m_RenderGraphTestPipeline.invalidContextForTesting = false;
            // Cleaning all Render Graph resources and data structures
            // Nothing remains, Render Graph in next test will start from scratch
            m_RenderGraph.CleanupResourcesAndGraph();
        }
    }
}