using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Sirenix.Utilities;
using Test.Util;
using UnityEditor;
using UnityEngine;
using VOID.Generic.Objects.Abstract;

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

        [OneTimeSetUp]
        public void Setup()
        {
            AssetDatabase.FindAssets("t:GameObject").ForEach(gameObjectGuid =>
            {
                var gameObjectPath = AssetDatabase.GUIDToAssetPath(gameObjectGuid);
                var gameObject = AssetDatabase.LoadAssetAtPath<GameObject>(gameObjectPath);
                var abstractObjects = gameObject.GetComponentsInChildren<AbstractObject>();
                
                // If prefab have any AbstractObject or any its children anywhere - take it, we will test it
                if (abstractObjects.Length > 0) _gameObjects.Add(gameObject);
            });
        }

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

        [Test]
        [Obsolete("Obsolete", false)]
        public void IsPrefab()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var isPrefab = PrefabUtility.GetPrefabParent(gameObject) == null &&
                               PrefabUtility.GetPrefabObject(gameObject) != null;

                if (isPrefab) return;

                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                Debug.LogError(
                    $"{prefabPathHyperlink} is not prefab, instances of '{nameof(AbstractObject)}' should be placed only on main GameObject of prefab",
                    gameObject);
            });
        }

        [Test]
        public void OnlyOnePerPrefab()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var abstractObject = gameObject.GetComponentsInChildren<AbstractObject>();
                
                if (abstractObject.Length == 1) return;
                
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                Debug.LogError($"Prefab: {prefabPathHyperlink}, prefab have attached more than one instance of '{nameof(AbstractObject)}'", gameObject);
            });
        }

        [Test]
        public void OnlyInPrefabRoot()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var abstractObject = gameObject.GetComponent<AbstractObject>();
                var insideAbstractObject = gameObject.GetComponentsInChildren<AbstractObject>().ToList();
                insideAbstractObject.Remove(abstractObject);
                
                if (insideAbstractObject.Count == 0) return;
                
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                Debug.LogError($"Prefab: {prefabPathHyperlink}, any instance of '{nameof(AbstractObject)}' should be placed ONLY on main GameObject of prefab, not inside its children", abstractObject);
            });
        }

        [Test]
        public void BasicData_DisplayName()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var abstractObject = gameObject.GetComponent<AbstractObject>();

                if (!abstractObject) return;

                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                var name = abstractObject.basicData.finalName;
                var trimName = name.Trim();

                if (!name.Equals(trimName))
                    Debug.LogWarning(
                        $"Prefab's display name trimmed is different (contain whitespaces at start or end): {prefabPathHyperlink}");

                if (trimName.Length > 0) return;

                Debug.LogError($"Prefab's display name is empty: {prefabPathHyperlink}", abstractObject);
            });
        }

        [Test]
        public void UnitData_Size()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var abstractObject = gameObject.GetComponent<AbstractObject>();
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                
                if (abstractObject.basicData.height <= 0f)
                    Debug.LogError($"{nameof(AbstractObject)}'s height is lesser or equal to zero: {prefabPathHyperlink}", abstractObject);
                
                if (abstractObject.basicData.radius <= 0f)
                    Debug.LogWarning($"{nameof(AbstractObject)}'s width is lesser or equal to zero: {prefabPathHyperlink}", abstractObject);
            });
        }
        
        [Test]
        public void BasicData_BasicResources()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var abstractObject = gameObject.GetComponent<AbstractObject>();

                if (!abstractObject) return;

                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                var health = abstractObject.basicData.currentResources.health;
                var baseHealth = abstractObject.basicData.baseMaxResources.health;

                if (baseHealth <= 0f)
                    Debug.LogError($"Prefab's baseHealth cannot be 0 or lower: {prefabPathHyperlink}", abstractObject);

                if (health > baseHealth)
                    Debug.LogError($"Prefab's current health is greater than baseMaxHealth: {prefabPathHyperlink}", abstractObject);
            });
        }

        [Test]
        public void StatusManager_DuplicatedStatuses()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var abstractObject = gameObject.GetComponent<AbstractObject>();
                if (!abstractObject) return;

                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                var statuses = abstractObject.statusManager.GetStatuses().ToList();
                
                if (statuses.Distinct().Count() != statuses.Count)
                    Debug.LogError($"Prefab's '{nameof(AbstractObjectStatusManager)}' contain duplicated statuses: {prefabPathHyperlink}", abstractObject);
            });
        }

        [Test]
        public void StatusManager_InvalidStatusCombination()
        {
            _gameObjects.ForEach(gameObject =>
            {
                var abstractObject = gameObject.GetComponent<AbstractObject>();
                if (!abstractObject) return;
            
                var prefabPath = AssetDatabase.GetAssetPath(gameObject.GetInstanceID());
                var prefabPathHyperlink = DebugLogUtil.Hyperlink(prefabPath);
                var statuses = abstractObject.statusManager.GetStatuses().ToList();

                statuses.ForEach(status =>
                {
                    statuses
                        .Where(otherStatus => otherStatus != status)
                        .Where(otherStatus => status.removes.Contains(otherStatus))
                        .ForEach(invalidStatus =>
                            Debug.LogError($"Invalid combination of '{status.displayName}' and '{invalidStatus.displayName}': {prefabPathHyperlink}", abstractObject));
                });
            });
        }
    }
}

