using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Eagle0.Tutorial;
using NUnit.Framework;
using TMPro;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace eagle0.Tests {
    [TestFixture]
    public class TutorialSpriteAssetTests {
        private const string SharedScenePath = "Assets/Scenes/Shared.unity";
        private const string TutorialSpriteAssetRoot = "Assets/Tutorial/Sprite Assets";
        private const string InternalErrorShaderName = "Hidden/InternalErrorShader";

        [Test]
        public void TutorialTmpSpriteAssetsHaveRenderableMaterials() {
            var violations = new List<string>();
            var paths = TutorialSpriteAssetPaths().ToList();
            if (paths.Count == 0) {
                violations.Add(
                        $"{TutorialSpriteAssetRoot}: must contain tutorial TMP sprite assets");
            }

            foreach (var path in paths) {
                var spriteAsset = AssetDatabase.LoadAssetAtPath<TMP_SpriteAsset>(path);
                if (spriteAsset == null) {
                    violations.Add($"{path}: must load as a TMP_SpriteAsset");
                    continue;
                }

                AppendSpriteAssetViolations(path, spriteAsset, violations);
            }

            Assert.IsEmpty(
                    violations,
                    "Tutorial TMP sprite assets should keep renderable materials under URP:\n" +
                            string.Join("\n", violations));
        }

        [Test]
        public void SharedDialoguePanelUsesRenderableInstructionSpriteAsset() {
            var scene = EditorSceneManager.OpenScene(SharedScenePath, OpenSceneMode.Single);
            var panels = Resources.FindObjectsOfTypeAll<DialoguePanelController>()
                                 .Where(panel => panel.gameObject.scene == scene)
                                 .ToList();
            Assert.IsNotEmpty(panels, $"{SharedScenePath} must contain a dialogue panel.");

            var violations = new List<string>();
            foreach (var panel in panels) {
                var path = GameObjectPath(panel.gameObject);
                if (panel.instructionSpriteAsset == null) {
                    violations.Add($"{path}: instructionSpriteAsset must be assigned");
                    continue;
                }

                AppendSpriteAssetViolations(path, panel.instructionSpriteAsset, violations);
            }

            Assert.IsEmpty(
                    violations,
                    "Tutorial dialogue panels should have renderable instruction sprite assets:\n" +
                            string.Join("\n", violations));
        }

        private static IEnumerable<string> TutorialSpriteAssetPaths() {
            return AssetDatabase.FindAssets("t:TMP_SpriteAsset", new[] { TutorialSpriteAssetRoot })
                    .Select(AssetDatabase.GUIDToAssetPath)
                    .Where(path => Path.GetExtension(path).Equals(
                                   ".asset",
                                   StringComparison.OrdinalIgnoreCase))
                    .OrderBy(path => path, StringComparer.Ordinal);
        }

        private static void AppendSpriteAssetViolations(
                string label,
                TMP_SpriteAsset spriteAsset,
                ICollection<string> violations) {
            if (spriteAsset.spriteSheet == null) {
                violations.Add($"{label}: must have a sprite sheet texture");
            }

            if (spriteAsset.spriteCharacterTable == null ||
                spriteAsset.spriteCharacterTable.Count == 0) {
                violations.Add($"{label}: must have at least one sprite character");
            }

            if (spriteAsset.spriteGlyphTable == null || spriteAsset.spriteGlyphTable.Count == 0) {
                violations.Add($"{label}: must have at least one sprite glyph");
            }

            var material = spriteAsset.material;
            if (material == null) {
                violations.Add($"{label}: must have a TMP sprite material");
                return;
            }

            if (material.shader == null) {
                violations.Add($"{label}: material '{material.name}' must have a shader");
                return;
            }

            if (material.shader.name == InternalErrorShaderName) {
                violations.Add(
                        $"{label}: material '{material.name}' must not use {InternalErrorShaderName}");
            }
        }

        private static string GameObjectPath(GameObject gameObject) {
            var names = new Stack<string>();
            var current = gameObject.transform;
            while (current != null) {
                names.Push(current.name);
                current = current.parent;
            }

            return string.Join("/", names);
        }
    }
}
