using System.Linq;
using UnityEngine;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Abstract;
using VOID.Generic.Objects.Door;
using VOID.Generic.Objects.Item;
using VOID.Generic.Objects.Unit.Actions.Exceptions;
using VOID.Generic.Objects.Unit.Events;
using VOID.Generic.Objects.Usable;
using VOID.Generic.Objects.Usable.Events;
using VOID.Generic.Util;
using VOID.ScriptableObjects;

namespace VOID.Generic.Objects.Unit.Actions
{
    public class UseAction : AbstractAction
    {
        private readonly UnitObject _unit;
        private readonly AbstractObject _targetObject;

        public UseAction(UnitObject unit, AbstractObject targetObject)
        {
            _unit = unit;
            _targetObject = targetObject;
        }

        public override bool IsInRange()
        {
            return RangeUtil.IsInRange(_unit, _targetObject, _unit.unitData.useRange);
        }

        protected override void OnEndIt() { }

        protected override void OnCancelIt() { }
        
        public override void CanDoIt()
        {
            Check(
                _unit.unitData.canUse,
                UnitActionErrorType.ThisUnitCantUse);
            Check(
                state == ActionState.Ready,
                UnitActionErrorType.ActionStateIsNotReady);
            if (_unit.basicState.turnManager)
            {
                Check(
                    _unit.unitState.isSelfTurn,
                    UnitActionErrorType.UnitSelfTurnNotActive);
                Check(
                    _unit.unitData.currentResources.actionPoints >= 1,
                    UnitActionErrorType.UnitNotEnoughActionPoints);
            }
            Check(
                _unit.unitState.isControllable,
                UnitActionErrorType.UnitStateIsNotControllable);
            Check(
                IsInRange(),
                UnitActionErrorType.NotInRange);
            Check(
                _targetObject is not UsableObject usable || usable.usableData.requirements.All(requirement => requirement.Check(_unit)),
                UnitActionErrorType.RequirementsNotMeetToUseThatItem);
        }

        protected override void OnDoIt()
        {
            if (_unit.basicState.turnManager)
                _unit.unitData.UseResource(UnitResourceType.ActionPoints, 1);
            
            if (_targetObject is not ItemObject item || item.itemState.carriedBy != _unit)
                _unit.OrderInstantly(new RotateAction(_unit, _targetObject));
            
            BeforePerform();
            switch (_targetObject)
            {
                case UsableObject usable:
                    UseUsable(usable);
                    break;
                case DoorObject door:
                    UseDoor(door);
                    break;
                default:
                    Debug.LogWarning($"Object '{nameof(_targetObject)}' cant be used.");
                    break;
            }
            
            AfterPerform();
            EndIt();
        }

        private void UseUsable(UsableObject usable)
        {
            var useEvent = new UseEvent
            {
                usedBy = _unit,
                usedObject = usable
            };

            Check(usable.itemData.stackSize >= usable.usableData.stacksPerUse, UnitActionErrorType.UsableNotEnoughStacks);

            useEvent.usedBy.unitEvents.onUseBefore.Invoke(useEvent);
            useEvent.usedObject.usableEvents.onUsedBefore.Invoke(useEvent);

            Check(!useEvent.prevented, UnitActionErrorType.UseIsPrevented);
            
            var equipStatus = ScriptableObject.CreateInstance<BasicStatus>();
            equipStatus.effects = usable.usableData.applyOnUse;
            equipStatus.hidden = true;
            _unit.statusManager.AddStatus(equipStatus, _unit, false);
            
            useEvent.usedBy.unitEvents.onUseAfter.Invoke(useEvent);
            useEvent.usedObject.usableEvents.onUsedAfter.Invoke(useEvent);

            usable.itemData.stackSize -= usable.usableData.stacksPerUse;
            
            if (usable.itemData.stackSize <= 0) usable.Remove();
        }

        private void UseDoor(DoorObject door)
        {
            if (door.doorState.isOpen)
                door.Close(_unit);
            else
                door.Open(_unit);
        }
    }
}