using System.IO;
using NUnit.Framework;
using UnityEditor;
using UnityEngine;

namespace eagle0.Tests {
    [TestFixture]
    public class RuntimeShaderValidationTests {
        private const string GraphicsSettingsPath = "ProjectSettings/GraphicsSettings.asset";
        private const string ShardokScenePath = "Assets/Scenes/Shardok.unity";
        private const string HexGridScriptPath = "Assets/Shardok/HexGrid.cs";
        private const string TrackedEffectAnimatorScriptPath =
                "Assets/Shardok/TrackedEffectAnimator.cs";

        private static readonly RuntimeShader[] RequiredRuntimeShaders = {
            new("Shardok/Foreground UI Overlay",
                "Assets/Shardok/Shaders/ForegroundUIOverlay.shader",
                mustBeAlwaysIncluded: true),
            new("TextMeshPro/Distance Field Overlay",
                "Assets/TextMesh Pro/Resources/Shaders/TMP_SDF Overlay.shader",
                mustBeAlwaysIncluded: true),
            new("Eagle/ProvinceParticle",
                "Assets/Eagle/Shaders/ProvinceParticleShader.shader",
                mustBeAlwaysIncluded: false),
        };

        [Test]
        public void RequiredRuntimeShaderAssetsExist() {
            foreach (var runtimeShader in RequiredRuntimeShaders) {
                var shader = AssetDatabase.LoadAssetAtPath<Shader>(runtimeShader.AssetPath);

                Assert.NotNull(
                        shader,
                        $"{runtimeShader.Name} must exist at {runtimeShader.AssetPath}.");
                Assert.AreEqual(
                        runtimeShader.Name,
                        shader.name,
                        $"{runtimeShader.AssetPath} should expose the expected shader name.");
            }
        }

        [Test]
        public void ShardokForegroundRuntimeShadersAreSerializedReferences() {
            AssertSerializedShaderReferences(ShardokScenePath);
        }

        [Test]
        public void ShardokForegroundRuntimeCodeDoesNotFindProjectShadersByName() {
            AssertShaderFindIsNotUsed(HexGridScriptPath);
            AssertShaderFindIsNotUsed(TrackedEffectAnimatorScriptPath);
        }

        [Test]
        public void PlayerRuntimeOnlyShadersAreAlwaysIncluded() {
            var graphicsSettingsText = File.ReadAllText(GraphicsSettingsPath);

            foreach (var runtimeShader in RequiredRuntimeShaders) {
                if (!runtimeShader.MustBeAlwaysIncluded) { continue; }

                var guid = AssetDatabase.AssetPathToGUID(runtimeShader.AssetPath);
                Assert.IsNotEmpty(guid, $"{runtimeShader.AssetPath} must have an asset GUID.");
                StringAssert.Contains(
                        guid,
                        graphicsSettingsText,
                        $"{runtimeShader.Name} must be listed in Always Included Shaders so player builds can resolve it.");
            }
        }

        private static void AssertSerializedShaderReferences(string assetPath) {
            var assetText = File.ReadAllText(assetPath);
            AssertSerializedShaderReference(
                    assetPath,
                    assetText,
                    "foregroundGraphicShader",
                    "Assets/Shardok/Shaders/ForegroundUIOverlay.shader");
            AssertSerializedShaderReference(
                    assetPath,
                    assetText,
                    "foregroundTextShader",
                    "Assets/TextMesh Pro/Resources/Shaders/TMP_SDF Overlay.shader");
            AssertSerializedShaderReference(
                    assetPath,
                    assetText,
                    "oneShotEffectShader",
                    "Assets/Shardok/Shaders/ForegroundUIOverlay.shader");
        }

        private static void AssertSerializedShaderReference(
                string assetPath,
                string assetText,
                string fieldName,
                string shaderPath) {
            var guid = AssetDatabase.AssetPathToGUID(shaderPath);
            Assert.IsNotEmpty(guid, $"{shaderPath} must have an asset GUID.");
            StringAssert.Contains(
                    $"{fieldName}: {{fileID: 4800000, guid: {guid}",
                    assetText,
                    $"{assetPath} must serialize {fieldName} to {shaderPath}.");
        }

        private static void AssertShaderFindIsNotUsed(string scriptPath) {
            StringAssert.DoesNotContain(
                    "Shader.Find",
                    File.ReadAllText(scriptPath),
                    $"{scriptPath} should use serialized shader references for project-owned runtime shaders.");
        }

        private readonly struct RuntimeShader {
            public RuntimeShader(string name, string assetPath, bool mustBeAlwaysIncluded) {
                Name = name;
                AssetPath = assetPath;
                MustBeAlwaysIncluded = mustBeAlwaysIncluded;
            }

            public string Name { get; }
            public string AssetPath { get; }
            public bool MustBeAlwaysIncluded { get; }
        }
    }
}
