using System;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using Sirenix.Utilities;
using UnityEngine;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Abstract.Events;
using VOID.Generic.Triggers;

namespace VOID.Generic.Objects.Abstract
{
    [SelectionBase]
    [DisallowMultipleComponent]
    [RequireComponent(typeof(Rigidbody))]
    public abstract class AbstractObject : MonoBehaviour
    {
        [Title("=== BasicObject properties ===")]
        public AbstractObjectData basicData;
        public AbstractObjectEvents basicEvents;
        public AbstractObjectStatusManager statusManager;
        public AbstractObjectState basicState;

        protected void OnValidate()
        {
            gameObject.GetComponent<Rigidbody>().hideFlags = HideFlags.NotEditable;
            
            // Init RigidBody
            var rb = GetComponent<Rigidbody>();
            rb.mass = 0;
            rb.linearDamping = 0;
            rb.angularDamping = 0;
            rb.automaticCenterOfMass = true;
            rb.automaticInertiaTensor = true;
            rb.useGravity = false;
            rb.isKinematic = true;
            rb.interpolation = RigidbodyInterpolation.None;
            rb.constraints = RigidbodyConstraints.FreezeAll;
        }

        private void Awake()
        {
            GetObjectComponents().ForEach(component => component.SetParent(this));
            GetObjectComponents().ForEach(component => component.DoSelfInitialize());
            GetObjectComponents().ForEach(component => component.DoEventInitialize());
            GetObjectComponents().ForEach(component => component.DoInitialize());
        }

        private void Start()
        {
            // Finally when this object is fully initialized then we can 
            basicEvents.onSpawn.Invoke();
        }

        protected void FixedUpdate()
        {
            basicEvents.onFixedUpdate.Invoke();
        }

        protected virtual List<ObjectComponent> GetObjectComponents()
        {
            return new List<ObjectComponent>
            {
                basicData,
                basicEvents,
                statusManager,
                basicState
            };
        }

        [Button]
        public void TakeDamage(DamageEvent damageEvent)
        {
            if (basicState.isDead) return;
            if (!basicData.canBeDestroyed) return;

            basicEvents.onDamagedBefore.Invoke(damageEvent);
            if (damageEvent.attacker) damageEvent.attacker.unitEvents.onDamageBefore.Invoke(damageEvent);

            if (damageEvent.prevented) return;

            var damage = damageEvent.amount;
            var resistance = 1 - basicData.resistances.Get(damageEvent.damageType);
            
            basicData.UseResource(BasicResourceType.Health, damage * resistance, true);
            
            basicEvents.onDamagedAfter.Invoke(damageEvent);
            if (damageEvent.attacker) damageEvent.attacker.unitEvents.onDamageAfter.Invoke(damageEvent);
            
            if (basicData.currentResources.health <= 0f) Death();
        }

        [Button]
        public void Death()
        {
            if (basicState.isDead) return;
            if (!basicData.canBeDestroyed) return;

            var deathEvent = new DeathEvent { obj = this };

            basicEvents.onDeathBefore.Invoke(deathEvent);
            if (deathEvent.prevented) return;
            basicEvents.onDeathAfter.Invoke(deathEvent);

            basicData.currentResources.health = 0f;
        }

        [Button]
        public void Revive()
        {
            if (basicState.isDead == false) return;

            basicData.SetResourcePercentage(BasicResourceType.Health, 0.3f);
            basicEvents.onRevive.Invoke(new ReviveEvent { obj = this });
        }

        [Button]
        public void Remove()
        {
            var removeEvent = new RemoveEvent { obj = this };
            
            basicEvents.onRemove.Invoke(removeEvent);
            
            Destroy(gameObject);
        }

        private void OnTriggerEnter(Collider other)
        {
            other.GetComponentsInParent<IDynamicTrigger>().ForEach(triggerArea => {
                if (basicState.isDead == false) triggerArea.OnObjectEnter(this);
                basicEvents.onTriggerEnter.Invoke(new TriggerEvent { obj = this, triggerArea = triggerArea });
            });
        }

        private void OnTriggerExit(Collider other)
        {
            other.GetComponentsInParent<IDynamicTrigger>().ForEach(triggerArea => {
                if (basicState.isDead == false) triggerArea.OnObjectExit(this);
                basicEvents.onTriggerExit.Invoke(new TriggerEvent { obj = this, triggerArea = triggerArea });
            });
        }
    }
}