using System;
using System.Linq;
using eagle;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace eagle0.Tests {
    [TestFixture]
    public class EagleMapMaterialWiringTests {
        private const string EagleScenePath = "Assets/Scenes/Eagle.unity";
        private const string ProvinceMapMaterialPath =
                "Assets/Eagle/Materials/ProvinceMapMaterial.mat";
        private const string ProvinceWeatherMapMaterialPath =
                "Assets/Eagle/Materials/ProvinceWeatherMapMaterial.mat";
        private const string ProvinceMapShaderName = "Eagle/ProvinceMap";
        private const string ProvinceWeatherMapShaderName = "Eagle/ProvinceWeatherMap";

        [Test]
        public void EagleSceneUsesProjectMapMaterials() {
            var scene = EditorSceneManager.OpenScene(EagleScenePath, OpenSceneMode.Single);
            var provinceMapMaterial = LoadMaterial(ProvinceMapMaterialPath, ProvinceMapShaderName);
            var weatherMapMaterial =
                    LoadMaterial(ProvinceWeatherMapMaterialPath, ProvinceWeatherMapShaderName);

            Assert.AreSame(
                    provinceMapMaterial,
                    FindSceneComponent<ProvinceIDLoader>(scene).provinceMapMaterial,
                    "ProvinceIDLoader should feed the project province map material.");
            Assert.AreSame(
                    weatherMapMaterial,
                    FindSceneComponent<ProvinceIDLoader>(scene).weatherMaterial,
                    "ProvinceIDLoader should feed the project weather map material.");
            Assert.AreSame(
                    provinceMapMaterial,
                    FindSceneComponent<ProvinceColorManager>(scene).provinceMapMaterial,
                    "ProvinceColorManager should update the project province map material.");
            Assert.AreSame(
                    provinceMapMaterial,
                    FindSceneComponent<ProvinceBorderManager>(scene).provinceMapMaterial,
                    "ProvinceBorderManager should update the project province map material.");
            Assert.AreSame(
                    provinceMapMaterial,
                    FindSceneComponent<ProvinceBorderDistanceGenerator>(scene).provinceMapMaterial,
                    "ProvinceBorderDistanceGenerator should update the project province map material.");
            Assert.AreSame(
                    weatherMapMaterial,
                    FindSceneComponent<ProvinceWeatherController>(scene).weatherMaterial,
                    "ProvinceWeatherController should update the project weather map material.");
        }

        [Test]
        public void MapAnimationRunsAfterModelUpdates() {
            Assert.Greater(
                    ExecutionOrderFor(typeof(MapController)),
                    ExecutionOrderFor(typeof(EagleGameController)),
                    "MapController must animate province colors after EagleGameController can " +
                            "replace the model and reset their base colors.");
        }

        private static int ExecutionOrderFor(Type componentType) {
            var attribute =
                    componentType.GetCustomAttributes(typeof(DefaultExecutionOrder), inherit: false)
                            .Cast<DefaultExecutionOrder>()
                            .SingleOrDefault();
            return attribute?.order ?? 0;
        }

        private static Material LoadMaterial(string path, string expectedShaderName) {
            var material = AssetDatabase.LoadAssetAtPath<Material>(path);
            Assert.NotNull(material, $"{path} must exist.");
            Assert.NotNull(material.shader, $"{path} must have a shader.");
            Assert.AreEqual(
                    expectedShaderName,
                    material.shader.name,
                    $"{path} should use the expected project shader.");
            return material;
        }

        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;
        }
    }
}
