using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Sirenix.Utilities;
using Test.Util;
using UnityEditor;
using UnityEngine;
using UnityEngine.AI;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Accessory;
using VOID.Generic.Objects.Armor;
using VOID.Generic.Objects.Item;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Objects.Unit.Animations;
using VOID.Generic.Objects.Unit.Equipment;
using VOID.Generic.Objects.Weapon;
using VOID.Generic.Objects.Wearable;

namespace Test.EditMode.Prefabs.Generic
{
    public class UnitObjectTest
    {
        private List<GameObject> _gameObjects = new();

        [OneTimeSetUp]
        public void Setup()
        {
            AssetDatabase.FindAssets($"t:{nameof(GameObject)}").ForEach(gameObjectGuid =>
            {
                var gameObjectPath = AssetDatabase.GUIDToAssetPath(gameObjectGuid);
                var gameObject = AssetDatabase.LoadAssetAtPath<GameObject>(gameObjectPath);
                var unitObjects = gameObject.GetComponentsInChildren<UnitObject>();

                // If prefab have any ArmorObject - take it, we will test it
                if (unitObjects.Length > 0) _gameObjects.Add(gameObject);
            });
        }

        [OneTimeTearDown]
        public void Cleanup()
        {
            _gameObjects?.Clear();
            _gameObjects = null;
        }

        [Test]
        public void UnitData_Portrait()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var unitObject = gameObject.GetComponent<UnitObject>();
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                
                if (!unitObject.basicData.image)
                    Debug.LogError($"{nameof(UnitObject)}'s portrait is not selected: {prefabPathHyperlink}", unitObject);
                
                if (unitObject.basicData.image.width > unitObject.basicData.image.height)
                    Debug.LogWarning($"{nameof(UnitObject)}'s portrait size proportions are invalid (width > height): {prefabPathHyperlink}");
            });
        }

        [Test]
        public void UnitData_Resources()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var unitObject = gameObject.GetComponent<UnitObject>();
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                var currentResources = unitObject.unitData.currentResources;
                var baseMaxResources = unitObject.unitData.baseMaxResources;

                Enum.GetValues(typeof(UnitResourceType))
                    .Cast<UnitResourceType>()
                    .Where(resourceType => currentResources.Get(resourceType) > baseMaxResources.Get(resourceType))
                    .ForEach(resourceType => Debug.LogError($"{nameof(UnitObject)}'s {resourceType} current value is greater than base max: {prefabPathHyperlink}", unitObject));
            });
        }

        [Test]
        public void UnitData_MainAttributes()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var unitObject = gameObject.GetComponent<UnitObject>();
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                var baseMainAttributes = unitObject.unitData.baseMainAttributes;

                Enum.GetValues(typeof(MainAttributeType))
                    .Cast<MainAttributeType>()
                    .Where(mainAttributeType => baseMainAttributes.Get(mainAttributeType) < 1)
                    .ForEach(mainAttributeType => Debug.LogError($"{nameof(UnitObject)}'s {mainAttributeType} is lesser than 1: {prefabPathHyperlink}", unitObject));
            });
        }

        [Test]
        public void UnitData_SubAttributes()
        {
            // Nothing to test
        }

        [Test]
        public void UnitData_Ranges()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var unitObject = gameObject.GetComponent<UnitObject>();
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                var useRange = unitObject.unitData.useRange;
                var talkRange = unitObject.unitData.talkRange;
                var takeRange = unitObject.unitData.takeRange;
                var inspectRange = unitObject.unitData.inspectRange;

                if (useRange < 1)
                    Debug.LogError($"{nameof(UnitObject)}'s use range is lesser than 1: {prefabPathHyperlink}", unitObject);

                if (talkRange < 1)
                    Debug.LogError($"{nameof(UnitObject)}'s talk range is lesser than 1: {prefabPathHyperlink}", unitObject);

                if (takeRange < 1)
                    Debug.LogError($"{nameof(UnitObject)}'s take range is lesser than 1: {prefabPathHyperlink}", unitObject);

                if (inspectRange < 1)
                    Debug.LogError($"{nameof(UnitObject)}'s inspect range is lesser than 1: {prefabPathHyperlink}", unitObject);
            });
        }

        [Test]
        public void UnitData_CastPosition()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var unitObject = gameObject.GetComponent<UnitObject>();
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                var castPosition = unitObject.unitData.castPosition;

                if (!castPosition)
                    Debug.LogError($"{nameof(UnitObject)}'s cast position is not selected: {prefabPathHyperlink}", unitObject);
            });
        }

        [Test]
        public void UnitAnimator_Events()
        {
            // Nothing to test
        }

        [Test]
        public void UnitAnimator_ActionQueue()
        {
            // Nothing to test
        }

        [Test]
        public void UnitAnimator_Animator()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var unitObject = gameObject.GetComponent<UnitObject>();
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);

                var animator = SerializedUtil.GetProperty<Animator>(unitObject, "unitAnimator.animator");
                if (!animator) 
                    Debug.LogError($"{nameof(UnitObject)}'s {nameof(UnitObjectAnimator)} have missing {nameof(Animator)}: {prefabPathHyperlink}", unitObject);
                
                var animatorCallbacks = SerializedUtil.GetProperty<UnitAnimatorCallbacks>(unitObject, "unitAnimator.unitAnimatorCallbacks");
                if (!animatorCallbacks) 
                    Debug.LogError($"{nameof(UnitObject)}'s {nameof(UnitObjectAnimator)} have missing {nameof(UnitAnimatorCallbacks)}: {prefabPathHyperlink}", unitObject);
            });
        }

        [Test]
        public void UnitNavMeshAgent_NavMeshAgent()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var unitObject = gameObject.GetComponent<UnitObject>();
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);

                var navMeshAgent = SerializedUtil.GetProperty<NavMeshAgent>(unitObject, "unitNavMeshAgent.navMeshAgent");
                if (navMeshAgent == null) 
                    Debug.LogError($"{nameof(UnitObject)}'s {nameof(UnitObjectNavAgent)} have missing {nameof(NavMeshAgent)}: {prefabPathHyperlink}", unitObject);
            });
        }

        [Test]
        public void UnitDataCalculator_Formulas_TODO()
        {
            // TODO: dodać testy
            Debug.LogWarning("TODO: zrobić weryfikacje formuł");
            _gameObjects.ForEach(gameObject =>
            {
                
            });
        }

        [Test]
        public void UnitBackpack_PredefinedItems()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var unitObject = gameObject.GetComponent<UnitObject>();
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                var itemPrefabs =
                    SerializedUtil.GetPropertyArray<GameObject>(unitObject, "unitBackpack.gameObjectItems");

                itemPrefabs.ForEach(prefab =>
                {
                    if (!prefab) return;
                    var itemObject = prefab.GetComponent<ItemObject>();
                    if (!itemObject)
                        Debug.LogError(
                            $"{nameof(UnitObject)}'s backpack: prefab '{prefab.name}' is not an {nameof(ItemObject)}: {prefabPathHyperlink}",
                            unitObject);
                });
            });
        }

        [Test]
        public void UnitEquipment_PredefinedWearables()
        {
            var canWearInSlot = new Dictionary<EquipmentSlotType, Func<WearableObject, bool>>
            {
                [EquipmentSlotType.MainHand] = wearable => wearable is WeaponObject,
                [EquipmentSlotType.OffHand] = wearable => wearable is WeaponObject,
                [EquipmentSlotType.Helmet] = wearable => wearable is ArmorObject helmet && helmet.armorData.armorType == ArmorType.Helmet,
                [EquipmentSlotType.Armor] = wearable => wearable is ArmorObject armor && armor.armorData.armorType == ArmorType.Armor,
                [EquipmentSlotType.Pants] = wearable => wearable is ArmorObject pants && pants.armorData.armorType == ArmorType.Pants,
                [EquipmentSlotType.Belt] = wearable => wearable is ArmorObject belt && belt.armorData.armorType == ArmorType.Belt,
                [EquipmentSlotType.Boots] = wearable => wearable is ArmorObject boots && boots.armorData.armorType == ArmorType.Boots,
                [EquipmentSlotType.Gloves] = wearable => wearable is ArmorObject gloves && gloves.armorData.armorType == ArmorType.Gloves,
                [EquipmentSlotType.Necklace] = wearable => wearable is AccessoryObject necklace && necklace.accessoryData.accessoryType == AccessoryType.Necklace,
                [EquipmentSlotType.RingOne] = wearable => wearable is AccessoryObject ring && ring.accessoryData.accessoryType == AccessoryType.Ring,
                [EquipmentSlotType.RingTwo] = wearable => wearable is AccessoryObject ring && ring.accessoryData.accessoryType == AccessoryType.Ring,
            };
            
            _gameObjects.ForEach(gameObject =>
            {
                var unitObject = gameObject.GetComponent<UnitObject>();
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);

                Enum.GetValues(typeof(EquipmentSlotType))
                    .Cast<EquipmentSlotType>()
                    .ForEach(equipmentSlotType =>
                    {
                        var equipmentSlotName = equipmentSlotType.ToString()[..1].ToLower() + equipmentSlotType.ToString()[1..];
                        var equippedItem = SerializedUtil.GetProperty<EquippedItem>(unitObject, $"unitEquipment.{equipmentSlotName}");
                        if (equippedItem == null || !equippedItem.gameObject) return;
                        var wearableObject = equippedItem.gameObject.GetComponent<WearableObject>();
                        var canBePlaced = canWearInSlot.GetValueOrDefault(equipmentSlotType).Invoke(wearableObject);
                        if (!wearableObject || !canBePlaced)
                            Debug.LogError(
                                $"{nameof(UnitObject)}'s equipment: prefab '{equippedItem.gameObject.name}' can't be placed in {equipmentSlotName}: {prefabPathHyperlink}",
                                unitObject);
                    });
            });
        }

        [Test]
        public void UnitAbilityBook_DefaultAttackAbility()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var unitObject = gameObject.GetComponent<UnitObject>();
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                var defaultAttack = unitObject.unitAbilityBook.defaultAttackAbility;

                if (defaultAttack == null)
                    Debug.LogError(
                        $"{nameof(UnitObject)}'s ability book: default attack ability is not selected: {prefabPathHyperlink}",
                        unitObject);
            });
        }
    }
}

