using System;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Smoothly rotates the player towards a valid enemy target when an attack animation is triggered.
/// </summary>
[DisallowMultipleComponent]
public sealed class FaceTargetOnAttack : MonoBehaviour
{
    private const float MinAssistDuration = 0.05f;
    private const float MinCancelTime = 0.05f;

    [Header("Assist Toggle")]
    [SerializeField] private bool enableAutoFaceOnAttack = true;
    [SerializeField] private bool reEvaluateDuringWindow = true;

    [Header("Rotation")]
    [SerializeField] private float rotationSpeedDegPerSec = 720f;
    [SerializeField] private float assistWindowSeconds = 0.25f;
    [SerializeField] private float manualOverrideThreshold = 0.15f;
    [SerializeField] private float targetLoseDistanceTolerance = 0.25f;

    [Header("Dependencies")]
    [SerializeField] private EnemyTargetDetector targetDetector;
    [SerializeField] private LookInputRouter lookInputRouter;
    [SerializeField] private CharacterAttacks characterAttacks;
    [SerializeField] private PlayerScript playerScript;
    [SerializeField] private Animator animator;

    [Header("Animator Output")]
    [SerializeField] private bool driveAnimatorTurnSpeed = false;
    [SerializeField] private string turnSpeedParameter = "TurnSpeed";
    [SerializeField] private float turnSpeedScale = 1f;

    [Header("Attack Filtering")]
    [Tooltip("Animations that should trigger auto facing. Leave empty to allow all.")]
    [SerializeField] private List<string> assistedAnimationNames = new List<string>
    {
        AnimationNames.Jab,
        AnimationNames.Haymaker,
        AnimationNames.SpinningBackfist,
        AnimationNames.ElbowSmash,
        AnimationNames.AirPunch,
        AnimationNames.LowKick,
        AnimationNames.BasicKick,
        AnimationNames.SumoSlap
    };

    private float assistTimer;
    private Transform currentTarget;
    private Vector3 currentTargetPosition;
    private Quaternion lastFrameRotation;
    private bool assistActive;
    private bool waitingForCancelWindow;

    private HashSet<string> assistedLookup;

    private void Reset()
    {
        targetDetector = GetComponent<EnemyTargetDetector>();
        lookInputRouter = GetComponent<LookInputRouter>();
        characterAttacks = GetComponent<CharacterAttacks>();
        playerScript = GetComponent<PlayerScript>();
        animator = GetComponent<Animator>();
    }

    private void Awake()
    {
        if (targetDetector == null)
            targetDetector = GetComponent<EnemyTargetDetector>();
        if (lookInputRouter == null)
            lookInputRouter = GetComponent<LookInputRouter>();
        if (characterAttacks == null)
            characterAttacks = GetComponent<CharacterAttacks>();
        if (playerScript == null)
            playerScript = GetComponent<PlayerScript>();
        if (animator == null)
            animator = GetComponent<Animator>();

        assistedLookup = assistedAnimationNames != null && assistedAnimationNames.Count > 0
            ? new HashSet<string>(assistedAnimationNames)
            : null;

        if (characterAttacks != null)
        {
            characterAttacks.AnimationAboutToPlay += OnAnimationAboutToPlay;
        }
    }

    private void OnDestroy()
    {
        if (characterAttacks != null)
        {
            characterAttacks.AnimationAboutToPlay -= OnAnimationAboutToPlay;
        }
    }

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

        if (!enableAutoFaceOnAttack)
        {
            CancelAssist();
            return;
        }

        if (playerScript != null && (!playerScript.attack || playerScript.dodge || playerScript.block))
        {
            CancelAssist();
            return;
        }

        assistTimer -= Time.deltaTime;
        if (assistTimer <= 0f)
        {
            CancelAssist();
            return;
        }

        if (lookInputRouter != null && lookInputRouter.TryGetLookDelta(out var lookDelta))
        {
            if (waitingForCancelWindow)
            {
                if (assistWindowSeconds - assistTimer > MinCancelTime && lookDelta.magnitude >= manualOverrideThreshold)
                {
                    CancelAssist();
                    return;
                }
            }
            else if (lookDelta.magnitude >= manualOverrideThreshold)
            {
                CancelAssist();
                return;
            }
        }

        if (!ValidateOrRefreshTarget())
        {
            CancelAssist();
            return;
        }

        Vector3 direction = currentTargetPosition - transform.position;
        direction.y = 0f;
        if (direction.sqrMagnitude < 0.0001f)
        {
            CancelAssist();
            return;
        }

        Quaternion desiredRotation = Quaternion.LookRotation(direction, Vector3.up);
        lastFrameRotation = transform.rotation;
        transform.rotation = Quaternion.RotateTowards(transform.rotation, desiredRotation, rotationSpeedDegPerSec * Time.deltaTime);

        waitingForCancelWindow = false;

        if (driveAnimatorTurnSpeed && animator != null)
        {
            float angleDelta = Quaternion.Angle(lastFrameRotation, transform.rotation);
            float turnSpeed = (angleDelta / Time.deltaTime) * turnSpeedScale;
            animator.SetFloat(turnSpeedParameter, turnSpeed);
        }
    }

    private void OnAnimationAboutToPlay(string animationName)
    {
        if (!enableAutoFaceOnAttack)
            return;

        if (!ShouldAssistForAnimation(animationName))
            return;

        if (targetDetector == null)
            return;

        if (assistWindowSeconds < MinAssistDuration)
            assistWindowSeconds = MinAssistDuration;

        if (targetDetector.TryFindBestTarget(out var target, out var position))
        {
            BeginAssist(target, position);
        }
    }

    private bool ShouldAssistForAnimation(string animationName)
    {
        if (string.IsNullOrEmpty(animationName))
            return false;

        if (assistedLookup == null)
            return true;

        return assistedLookup.Contains(animationName);
    }

    private void BeginAssist(Transform target, Vector3 position)
    {
        currentTarget = target;
        currentTargetPosition = position;
        assistTimer = assistWindowSeconds;
        assistActive = true;
        waitingForCancelWindow = true;
        lastFrameRotation = transform.rotation;

        if (lookInputRouter != null)
        {
            lookInputRouter.ResetLookDelta();
        }
    }

    private bool ValidateOrRefreshTarget()
    {
        if (currentTarget == null || !currentTarget.gameObject.activeInHierarchy)
            return false;

        if (targetDetector == null)
            return false;

        float sqrDistance = (currentTargetPosition - transform.position).sqrMagnitude;
        float maxDistance = targetDetector.AssistRadius + targetLoseDistanceTolerance;
        if (sqrDistance > maxDistance * maxDistance)
            return false;

        if (reEvaluateDuringWindow && targetDetector.TryFindBestTarget(out var newTarget, out var newPosition))
        {
            currentTarget = newTarget;
            currentTargetPosition = newPosition;
        }
        else
        {
            currentTargetPosition = currentTarget.position;
        }

        return true;
    }

    private void CancelAssist()
    {
        assistActive = false;
        assistTimer = 0f;
        currentTarget = null;
        currentTargetPosition = Vector3.zero;
        waitingForCancelWindow = false;

        if (driveAnimatorTurnSpeed && animator != null)
        {
            animator.SetFloat(turnSpeedParameter, 0f);
        }
    }
}
