using System.Reflection;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.Rendering;

namespace eagle0.Tests {
    [TestFixture]
    public class BeastMaterialCompatibilityTests {
        private const string CompatibilityTypeName = "eagle.BeastMaterialCompatibility";
        private const string UniversalLitShaderName = "Universal Render Pipeline/Lit";
        private const string UniversalParticleUnlitShaderName =
                "Universal Render Pipeline/Particles/Unlit";
        private const string UniversalUnlitShaderName = "Universal Render Pipeline/Unlit";
        private const string BuiltInStandardShaderName = "Standard";

        [Test]
        public void AllowedShaderFindTargetsResolve() {
            AssertUrpActive();
            AssertShaderFindTargetResolves(UniversalLitShaderName);
            AssertShaderFindTargetResolves(UniversalParticleUnlitShaderName);
            AssertShaderFindTargetResolves(UniversalUnlitShaderName);
            AssertShaderFindTargetResolves(BuiltInStandardShaderName);
        }

        [Test]
        public void ConfigureRenderer_ReplacesBuiltInMaterialWithUrpLitMaterial() {
            AssertUrpActive();
            var sourceMaterial = new Material(Shader.Find(BuiltInStandardShaderName)) {
                color = Color.red,
                mainTexture = CreateTexture(Color.blue),
            };
            var gameObject = GameObject.CreatePrimitive(PrimitiveType.Cube);

            try {
                var renderer = gameObject.GetComponent<MeshRenderer>();
                renderer.sharedMaterial = sourceMaterial;

                InvokeCompatibilityMethod("ConfigureRenderer", renderer);

                Assert.AreEqual(UniversalLitShaderName, renderer.sharedMaterial.shader.name);
                AssertTextureEquals(sourceMaterial.mainTexture, renderer.sharedMaterial);
                AssertColorEquals(Color.red, renderer.sharedMaterial.color);
            } finally {
                Object.DestroyImmediate(gameObject);
                Object.DestroyImmediate(sourceMaterial.mainTexture);
                Object.DestroyImmediate(sourceMaterial);
            }
        }

        [Test]
        public void CreateParticleMaterial_UsesUrpParticleShaderAndTexture() {
            AssertUrpActive();
            var texture = CreateTexture(Color.green);
            var sourceMaterial = new Material(Shader.Find(BuiltInStandardShaderName)) {
                mainTexture = texture,
                color = Color.yellow,
            };

            var material = InvokeCompatibilityMethod<Material>(
                    "CreateParticleMaterial",
                    sourceMaterial,
                    texture);

            try {
                Assert.NotNull(material);
                StringAssert.StartsWith("Universal Render Pipeline/", material.shader.name);
                AssertTextureEquals(texture, material);
                Assert.AreEqual((int)RenderQueue.Transparent, material.renderQueue);
            } finally {
                Object.DestroyImmediate(material);
                Object.DestroyImmediate(sourceMaterial);
                Object.DestroyImmediate(texture);
            }
        }

        private static void AssertUrpActive() {
            var pipeline = GraphicsSettings.currentRenderPipeline;
            Assert.NotNull(pipeline, "URP must be configured as the active render pipeline.");
            StringAssert.Contains("UniversalRenderPipeline", pipeline.GetType().Name);
        }

        private static void AssertShaderFindTargetResolves(string shaderName) {
            Assert.NotNull(
                    Shader.Find(shaderName),
                    $"{shaderName} must resolve because BeastMaterialCompatibility is allowed to use this Unity/URP shader-name lookup.");
        }

        private static Texture2D CreateTexture(Color color) {
            var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
            texture.SetPixel(0, 0, color);
            texture.Apply();
            return texture;
        }

        private static void AssertTextureEquals(Texture expected, Material material) {
            var actual = material.HasProperty("_BaseMap") ? material.GetTexture("_BaseMap")
                                                          : material.GetTexture("_MainTex");
            Assert.AreSame(expected, actual);
        }

        private static void AssertColorEquals(Color expected, Color actual) {
            Assert.AreEqual(expected.r, actual.r, 0.001f);
            Assert.AreEqual(expected.g, actual.g, 0.001f);
            Assert.AreEqual(expected.b, actual.b, 0.001f);
            Assert.AreEqual(expected.a, actual.a, 0.001f);
        }

        private static void InvokeCompatibilityMethod(string methodName, params object[] args) {
            InvokeCompatibilityMethod<object>(methodName, args);
        }

        private static T InvokeCompatibilityMethod<T>(string methodName, params object[] args) {
            var type = typeof(eagle.BeastsEffect).Assembly.GetType(CompatibilityTypeName);
            Assert.NotNull(type, $"{CompatibilityTypeName} must exist.");

            var method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
            Assert.NotNull(method, $"{CompatibilityTypeName}.{methodName} must exist.");
            return (T)method.Invoke(null, args);
        }
    }
}
