using System;
using System.Collections.Generic;
using Grid;
using UnityEngine;
using UnityEngine.Serialization;

namespace Actions {
    public class MoveAction : BaseAction {
        private const float moveSpeed = 4f;
        private const float rotationSpeed = 10f;
        private const float stoppingDistance = 0.1f;
        private const int pathfindingDistanceMultiplier = 10;
        private const float DIFFERENT_FLOORS_TELEPORT_TIMER_MAX = .5f;

        [FormerlySerializedAs("MaxMoveDistance")] [SerializeField]
        private int maxMoveDistance = 4;

        private int currentPositionIndex;
        private float differentFloorsTeleportTimer;
        private bool isChangingFloors;
        private List<Vector3> PositionList { get; set; }


        protected override void Awake() {
            base.Awake();
            ActionName = "Move";
        }

        private void Update() {
            if (!IsActive) return;

            Vector3 targetPosition = PositionList[currentPositionIndex];
            if (isChangingFloors) {
                //Stop and teleport logic
                differentFloorsTeleportTimer -= Time.deltaTime;
                Vector3 targetSameFloorPosition = targetPosition;
                targetSameFloorPosition.y = transform.position.y;
                Vector3 rotateDirection = (targetSameFloorPosition - Unit.GetWorldPosition()).normalized;
                transform.forward = Vector3.Slerp(transform.forward, rotateDirection, rotationSpeed * Time.deltaTime);
                if (differentFloorsTeleportTimer < 0f) {
                    isChangingFloors = false;
                    transform.position = targetPosition;
                }
            }
            else {
                //Regular move logic
                Vector3 moveDirection = (targetPosition - Unit.GetWorldPosition()).normalized;
                transform.forward = Vector3.Slerp(transform.forward, moveDirection, rotationSpeed * Time.deltaTime); //Rotate towards targetPosition
                transform.position += moveSpeed * Time.deltaTime * moveDirection; //Move towards targetPosition
            }

            if (!(Vector3.Distance(targetPosition, Unit.GetWorldPosition()) < stoppingDistance)) return;

            currentPositionIndex++;
            if (currentPositionIndex >= PositionList.Count) {
                OnStopMoving?.Invoke(this, EventArgs.Empty);
                ActionComplete();
            }
            else {
                targetPosition = PositionList[currentPositionIndex];
                GridPosition targetGridPosition = LevelGrid.Instance.GetGridPosition(targetPosition);
                GridPosition unitGridPosition = LevelGrid.Instance.GetGridPosition(transform.position);
                if (targetGridPosition.Floor == unitGridPosition.Floor) return; //Same floor
                isChangingFloors = true;
                differentFloorsTeleportTimer = DIFFERENT_FLOORS_TELEPORT_TIMER_MAX;
                OnChangeFloorsStarted?.Invoke(this, new() { unitGridPosition = unitGridPosition, targetGridPosition = targetGridPosition });
            }
        }

        public event EventHandler OnStartMoving;
        public event EventHandler OnStopMoving;
        public event EventHandler<ChangeFloorsStartedEventArgs> OnChangeFloorsStarted;

        public override List<GridPosition> GetValidActionGridPositionList() {
            List<GridPosition> validGridPositionList = new();
            GridPosition unitGridPosition = Unit.GridPosition;

            for (int xPosition = -maxMoveDistance; xPosition <= maxMoveDistance; xPosition++) {
                for (int zPosition = -maxMoveDistance; zPosition <= maxMoveDistance; zPosition++) {
                    for (int floor = -maxMoveDistance; floor < maxMoveDistance; floor++) {
                        GridPosition offsetGridPosition = new(xPosition, zPosition, floor);
                        GridPosition testGridPosition = unitGridPosition + offsetGridPosition;

                        if (!LevelGrid.Instance.IsValidGridPosition(testGridPosition)) continue; //Only return valid grid positions
                        if (unitGridPosition == testGridPosition) continue; //Same grid position where the unit is already at
                        if (LevelGrid.Instance.HasAnyUnitOnGridPosition(testGridPosition)) continue; //Grid position already occopied with anther unit
                        if (!Pathfinding.Instance.IsWalkableGridPosition(testGridPosition)) continue; //Obstacle on path
                        if (!Pathfinding.Instance.HasPath(unitGridPosition, testGridPosition)) continue; //No path available

                        if (Pathfinding.Instance.GetPathLength(unitGridPosition, testGridPosition) > maxMoveDistance * pathfindingDistanceMultiplier) continue; //Pathlength is to long

                        validGridPositionList.Add(testGridPosition);
                    }
                }
            }

            return validGridPositionList;
        }

        public override void TakeAction(GridPosition gridPosition, Action onMoveCompleted) {
            List<GridPosition> pathGridPositionList = Pathfinding.Instance.FindPath(Unit.GridPosition, gridPosition, out _);

            currentPositionIndex = 0;
            PositionList = new();

            foreach (GridPosition pathGridPosition in pathGridPositionList) {
                PositionList.Add(LevelGrid.Instance.GetWorldPosition(pathGridPosition));
            }

            OnStartMoving?.Invoke(this, EventArgs.Empty);
            ActionStart(onMoveCompleted);
        }

        protected override EnemyAIAction GetEnemyAIAction(GridPosition gridPosition) {
            int targetCountAtGridPosition = Unit.GetAction<ShootAction>().GetTargetCountAtPosition(gridPosition);
            return new(gridPosition, targetCountAtGridPosition * 10);
        }

        public class ChangeFloorsStartedEventArgs : EventArgs {
            public GridPosition targetGridPosition;
            public GridPosition unitGridPosition;
        }
    }
}