using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Collider2D))]
[RequireComponent(typeof(Rigidbody2D))]
public class Enemy : MonoBehaviour, IDamageable
{
    [Header("Stats")]
    [SerializeField] private int _maxHealth = 30;

    [Header("Hit Feedback")]
    [SerializeField] private float _hitFlashDuration = 0.1f;
    [SerializeField] private Color _hitFlashColor = Color.red;

    [Header("Hit Bounce")]
    [SerializeField] private float _hitReactionDuration = 0.5f;
    [SerializeField] private float _airborneHitYVelocity = 3f;
    [SerializeField] private float _wallBounceVelocityMultiplier = 0.8f;
    [SerializeField] private float _wallBounceMinXVelocity = 1f;
    [SerializeField] private float _wallBounceUpwardVelocity = 1.5f;

    private int _currentHealth;
    private Rigidbody2D _rb;
    private Animator _anim;
    private SpriteRenderer _spriteRenderer;
    private Color _originalColor;
    private float _flashTimer;
    private float _hitReactionTimer;
    private bool _isGrounded;
    private Vector2 _lastVelocity;
    private Collider2D[] _bodyColliders;
    private readonly List<RaycastHit2D> _castResults = new();
    private const float HitPositionSkinWidth = 0.02f;
    private bool _isHitPositionCorrecting;
    private bool _correctHitPositionY;
    private float _hitPositionCorrectionTimer;
    private float _hitPositionCorrectionDuration;
    private Vector2 _hitPositionCorrectionStart;
    private Vector2 _hitPositionCorrectionTarget;

    private void Awake()
    {
        _currentHealth = _maxHealth;
        _rb = GetComponent<Rigidbody2D>();
        _anim = GetComponentInChildren<Animator>();
        _spriteRenderer = GetComponentInChildren<SpriteRenderer>();
        _bodyColliders = GetComponentsInChildren<Collider2D>();
        if (_spriteRenderer != null)
            _originalColor = _spriteRenderer.color;
    }

    private void Update()
    {
        if (_flashTimer > 0f)
        {
            _flashTimer -= Time.deltaTime;
            if (_flashTimer <= 0f && _spriteRenderer != null)
                _spriteRenderer.color = _originalColor;
        }

        if (_hitReactionTimer > 0f)
            _hitReactionTimer -= Time.deltaTime;
    }

    private void FixedUpdate()
    {
        if (_rb != null)
        {
            ApplySmoothHitPositionCorrection();
            _lastVelocity = _rb.linearVelocity;
        }
    }

    public void TakeDamage(int amount, Vector2 hitVelocity = default, string hitReactionAnimationState = null, Vector2? hitTargetPosition = null, bool correctHitTargetY = false, int hitPositionSolidMask = 0, float hitPositionCorrectionDuration = 0f)
    {
        if (_currentHealth <= 0) return;

        _currentHealth -= amount;
        Debug.Log($"{name} 피격: -{amount} (HP: {_currentHealth}/{_maxHealth})");

        if (_spriteRenderer != null)
        {
            _spriteRenderer.color = _hitFlashColor;
            _flashTimer = _hitFlashDuration;
        }

        if (_anim != null && !string.IsNullOrEmpty(hitReactionAnimationState))
            _anim.Play(hitReactionAnimationState);

        // 새 피격이 반응 속도를 전부 결정하므로, 이전 튕김/넉백 속도는 먼저 제거한다.
        if (_rb != null)
        {
            _hitReactionTimer = 0f;
            _rb.linearVelocity = Vector2.zero;
            _lastVelocity = Vector2.zero;

            BeginHitTargetPositionCorrection(hitTargetPosition, correctHitTargetY, hitPositionSolidMask, hitPositionCorrectionDuration);

            Vector2 nextVelocity = GetHitReactionVelocity(hitVelocity);
            if (nextVelocity != Vector2.zero)
            {
                _rb.linearVelocity = nextVelocity;
                _lastVelocity = nextVelocity;
                _hitReactionTimer = _hitReactionDuration;
            }
        }

        if (_currentHealth <= 0)
            Die();
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        UpdateGroundedState(collision);

        if (_hitReactionTimer <= 0f || _rb == null) return;
        if (collision.collider.GetComponentInParent<PlayerController>() != null) return;

        for (int i = 0; i < collision.contactCount; i++)
        {
            Vector2 normal = collision.GetContact(i).normal;
            if (Mathf.Abs(normal.x) < 0.5f) continue;

            BounceOffWall(normal);
            return;
        }
    }

    private void OnCollisionStay2D(Collision2D collision)
    {
        UpdateGroundedState(collision);
    }

    private void OnCollisionExit2D(Collision2D collision)
    {
        _isGrounded = false;
    }

    private Vector2 GetHitReactionVelocity(Vector2 hitVelocity)
    {
        // 공중 추가타는 고정된 세로 속도를 쓰되, 이전 물리 속도는 절대 이어받지 않는다.
        Vector2 nextVelocity = hitVelocity;
        if (!_isGrounded)
            nextVelocity.y = _airborneHitYVelocity;

        return nextVelocity;
    }

    private void BeginHitTargetPositionCorrection(Vector2? hitTargetPosition, bool correctHitTargetY, int solidMask, float correctionDuration)
    {
        _isHitPositionCorrecting = false;
        if (!hitTargetPosition.HasValue || _rb == null) return;

        Vector2 targetPosition = hitTargetPosition.Value;
        if (!correctHitTargetY)
            targetPosition.y = _rb.position.y;

        targetPosition = GetSafeHitTargetPosition(targetPosition, solidMask);
        if ((targetPosition - _rb.position).sqrMagnitude <= 0.0001f) return;

        if (correctionDuration <= 0f)
        {
            _rb.position = targetPosition;
            return;
        }

        _isHitPositionCorrecting = true;
        _correctHitPositionY = correctHitTargetY;
        _hitPositionCorrectionTimer = 0f;
        _hitPositionCorrectionDuration = correctionDuration;
        _hitPositionCorrectionStart = _rb.position;
        _hitPositionCorrectionTarget = targetPosition;
    }

    private void ApplySmoothHitPositionCorrection()
    {
        if (!_isHitPositionCorrecting || _rb == null) return;

        _hitPositionCorrectionTimer += Time.fixedDeltaTime;
        float normalizedTime = Mathf.Clamp01(_hitPositionCorrectionTimer / Mathf.Max(_hitPositionCorrectionDuration, Time.fixedDeltaTime));
        float easedTime = Mathf.SmoothStep(0f, 1f, normalizedTime);
        Vector2 nextPosition = _rb.position;
        nextPosition.x = Mathf.Lerp(_hitPositionCorrectionStart.x, _hitPositionCorrectionTarget.x, easedTime);
        if (_correctHitPositionY)
            nextPosition.y = Mathf.Lerp(_hitPositionCorrectionStart.y, _hitPositionCorrectionTarget.y, easedTime);

        _rb.MovePosition(nextPosition);

        if (normalizedTime >= 1f)
            _isHitPositionCorrecting = false;
    }

    private Vector2 GetSafeHitTargetPosition(Vector2 targetPosition, int solidMask)
    {
        if (solidMask == 0) return targetPosition;

        Vector2 startPosition = _rb.position;
        Vector2 moveDelta = targetPosition - startPosition;
        float distance = moveDelta.magnitude;
        if (distance <= 0.001f) return targetPosition;

        Vector2 direction = moveDelta / distance;
        float closestDistance = GetClosestBodyCastDistance(direction, distance + HitPositionSkinWidth, solidMask);
        if (closestDistance >= distance + HitPositionSkinWidth)
            return targetPosition;

        float allowedDistance = Mathf.Max(closestDistance - HitPositionSkinWidth, 0f);
        return startPosition + direction * allowedDistance;
    }

    private float GetClosestBodyCastDistance(Vector2 direction, float distance, int solidMask)
    {
        if (_bodyColliders == null || _bodyColliders.Length == 0)
            _bodyColliders = GetComponentsInChildren<Collider2D>();

        ContactFilter2D filter = new ContactFilter2D
        {
            useLayerMask = true,
            layerMask = solidMask,
            useTriggers = false
        };

        float closest = float.PositiveInfinity;
        for (int i = 0; i < _bodyColliders.Length; i++)
        {
            Collider2D bodyCollider = _bodyColliders[i];
            if (bodyCollider == null || bodyCollider.isTrigger) continue;

            _castResults.Clear();
            int hitCount = bodyCollider.Cast(direction, filter, _castResults, distance);
            for (int j = 0; j < hitCount; j++)
            {
                if (_castResults[j].distance < closest)
                    closest = _castResults[j].distance;
            }
        }

        return closest;
    }

    private void UpdateGroundedState(Collision2D collision)
    {
        for (int i = 0; i < collision.contactCount; i++)
        {
            if (collision.GetContact(i).normal.y > 0.5f)
            {
                _isGrounded = true;
                return;
            }
        }
    }

    private void BounceOffWall(Vector2 wallNormal)
    {
        // 피격 반응 중 옆벽에 부딪히면 현재 넉백 속도를 반사한다.
        Vector2 incomingVelocity = _lastVelocity.sqrMagnitude > _rb.linearVelocity.sqrMagnitude
            ? _lastVelocity
            : _rb.linearVelocity;

        if (Mathf.Abs(incomingVelocity.x) < _wallBounceMinXVelocity) return;

        Vector2 bouncedVelocity = Vector2.Reflect(incomingVelocity, wallNormal) * _wallBounceVelocityMultiplier;
        if (bouncedVelocity.y < _wallBounceUpwardVelocity)
            bouncedVelocity.y = _wallBounceUpwardVelocity;

        _rb.linearVelocity = bouncedVelocity;
        _hitReactionTimer = _hitReactionDuration;
    }

    private void Die()
    {
        Debug.Log($"{name} 사망");
        Destroy(gameObject);
    }
}
