using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using eagle;
using NUnit.Framework;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace eagle0.Tests {
    [TestFixture]
    public class ProvinceActionAnimatorSpriteTests {
        private const string EagleScenePath = "Assets/Scenes/Eagle.unity";
        private const string CombatSfxLabel = "sfx-combat";

        [Test]
        public void EagleSceneProvinceActionAnimatorHasVisualSpritesAssigned() {
            var scene = EditorSceneManager.OpenScene(EagleScenePath, OpenSceneMode.Single);
            var animator = FindSceneComponent<ProvinceActionAnimator>(scene);
            var violations = new List<string>();

            foreach (var field in PublicInstanceFields()) {
                if (field.FieldType == typeof(Sprite)) {
                    if (field.GetValue(animator) == null) {
                        violations.Add($"{field.Name} must be assigned.");
                    }
                } else if (field.FieldType == typeof(Sprite[])) {
                    var sprites = (Sprite[])field.GetValue(animator);
                    if (sprites == null || sprites.Length == 0) {
                        violations.Add($"{field.Name} must contain at least one sprite.");
                        continue;
                    }

                    for (var i = 0; i < sprites.Length; i++) {
                        if (sprites[i] == null) {
                            violations.Add($"{field.Name}[{i}] must be assigned.");
                        }
                    }
                }
            }

            Assert.IsEmpty(
                    violations,
                    "Eagle action animation sprites should stay assigned so URP visual QA covers real effects:\n" +
                            string.Join("\n", violations));
        }

        [Test]
        public void EagleSceneStrategicSoundNamesResolveToAddressableSfx() {
            var scene = EditorSceneManager.OpenScene(EagleScenePath, OpenSceneMode.Single);
            var animator = FindSceneComponent<ProvinceActionAnimator>(scene);
            var addressableSoundNames = AddressableAssetNamesForLabel(CombatSfxLabel);

            var requiredSoundNames = animator.actionSounds.Select(sound => sound.clipName)
                                             .Append("melee")
                                             .Append("archery")
                                             .Where(name => !string.IsNullOrEmpty(name))
                                             .Distinct(StringComparer.Ordinal)
                                             .OrderBy(name => name, StringComparer.Ordinal)
                                             .ToList();

            var missingSoundNames =
                    requiredSoundNames
                            .Where(soundName => !addressableSoundNames.Contains(soundName))
                            .ToList();

            Assert.IsEmpty(
                    missingSoundNames,
                    "Strategic map sounds are loaded by Addressables label/name, so every configured clipName must match an sfx-combat asset address filename:\n" +
                            string.Join("\n", missingSoundNames));
        }

        private static IEnumerable<FieldInfo> PublicInstanceFields() {
            return typeof(ProvinceActionAnimator)
                    .GetFields(BindingFlags.Instance | BindingFlags.Public)
                    .Where(field => field.FieldType == typeof(Sprite) ||
                                    field.FieldType == typeof(Sprite[]))
                    .OrderBy(field => field.Name, StringComparer.Ordinal);
        }

        private static T FindSceneComponent<T>(Scene scene)
                where T : Component {
            var component = Resources.FindObjectsOfTypeAll<T>().FirstOrDefault(
                    item => item.gameObject.scene == scene);
            Assert.NotNull(component, $"{scene.path} must contain {typeof(T).Name}.");
            return component;
        }

        private static HashSet<string> AddressableAssetNamesForLabel(string label) {
            var settings = AddressableAssetSettingsDefaultObject.Settings;
            Assert.NotNull(settings, "Addressable asset settings must exist.");
            Assert.Contains(
                    label,
                    settings.GetLabels(),
                    $"Addressables must define the '{label}' label.");

            return settings.groups.Where(group => group != null)
                    .SelectMany(group => group.entries)
                    .Where(entry => entry.labels.Contains(label))
                    .Select(entry => Path.GetFileNameWithoutExtension(entry.address))
                    .ToHashSet(StringComparer.Ordinal);
        }
    }
}
