using System.Collections.Generic;
using UnityEngine;

// ============================================================================
// Enemy
// ----------------------------------------------------------------------------
// 적 캐릭터의 모든 행동을 관리. 현재는 AI 이동 없음 (정지 표적).
// 책임:
//   - IDamageable 구현 → 공격 받기
//   - 피격 시각 효과 (색 깜빡)
//   - 피격 시 넉백 및 위치 보정
//   - 벽 충돌 시 반사 (튕기는 효과)
//   - 다른 적과의 소프트 분리 (한 점에 겹치지 않도록)
//   - 잡기 상태 처리 (플레이어가 강제로 끌고 다님)
//   - 사망 처리 (Health.OnDied 이벤트로 트리거)
//
// HP는 Health 컴포넌트로 분리. Enemy는 Health.TakeDamage를 위임 호출.
// ============================================================================
[RequireComponent(typeof(Collider2D))]
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Health))]
public class Enemy : MonoBehaviour, IDamageable
{
    // ─── 피격 시각 효과 ──────────────────────────────────────────────────
    [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;          // 공중에서 맞을 때 강제 Y 속도 (띄우기 효과)
    [SerializeField] private float _wallBounceVelocityMultiplier = 0.8f;// 벽에 부딪힐 때 속도 감쇠
    [SerializeField] private float _wallBounceMinXVelocity = 1f;        // 이 값보다 느리면 반사 안 함 (작은 충돌 무시)
    [SerializeField] private float _wallBounceUpwardVelocity = 1.5f;    // 반사 후 최소 Y 속도 (위로 살짝 튀게)

    // ─── 다른 적과의 시각적 분리 ─────────────────────────────────────────
    // 같은 위치에 적이 겹쳐 보이지 않도록 살짝 옆으로 미는 힘.
    [Header("Separation")]
    [SerializeField] private float _separationRadius = 0.6f;            // 이 거리 안의 다른 적과 분리
    [SerializeField] private float _separationStrength = 2f;            // 분리 강도 (units/sec)
    [SerializeField] private LayerMask _separationLayer;                // 검사 대상 레이어 (보통 Enemy)
    private static readonly Collider2D[] _separationBuffer = new Collider2D[16];  // OverlapCircle 결과 버퍼 (GC 회피)

    // ─── 사망 시 드랍 ───────────────────────────────────────────────────
    [Header("Drop")]
    [SerializeField] private WeaponData _dropWeapon;         // null이면 드랍 안 함
    [SerializeField] private WeaponPickup _weaponPickupPrefab;  // 픽업 오브젝트 프리팹 (한 종류 공유 가능)

    private Health _health;        // HP 컴포넌트 (별도 분리 → 플레이어도 같은 컴포넌트 재사용 가능)
    private Rigidbody2D _rb;
    private Animator _anim;
    private SpriteRenderer _spriteRenderer;
    private Color _originalColor;  // hit flash 끝나면 복귀할 원본 색
    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 bool _isGrabbed;            // 플레이어에게 잡힌 상태인지
    private Vector2 _grabTargetPosition;// 잡힌 동안 강제로 위치할 좌표
    private int _grabSolidMask;         // 잡혀서 이동할 때 벽에 끼이지 않게 검사할 솔리드 레이어

    public bool IsDead => _health != null && _health.IsDead;
    public bool IsGrabbed => _isGrabbed;
    public bool IsInHitReaction => _hitReactionTimer > 0f || _isHitPositionCorrecting || _flashTimer > 0f;
    public bool CanUseAI => !IsDead && !_isGrabbed && !IsInHitReaction;


    // 컴포넌트 캐싱 + Health.OnDied 구독 (사망 시 HandleDeath 자동 호출).
    private void Awake()
    {
        _health = GetComponent<Health>();
        _health.OnDied += HandleDeath;
        _rb = GetComponent<Rigidbody2D>();
        _anim = GetComponentInChildren<Animator>();
        _spriteRenderer = GetComponentInChildren<SpriteRenderer>();
        _bodyColliders = GetComponentsInChildren<Collider2D>();
        if (_spriteRenderer != null)
            _originalColor = _spriteRenderer.color;
    }

    // 이벤트 구독 해제 (Destroy 시 누수 방지).
    private void OnDestroy()
    {
        if (_health != null)
            _health.OnDied -= HandleDeath;
    }

    // 매 프레임: hit flash 타이머 + hit reaction 타이머 카운트다운.
    private void Update()
    {
        if (_flashTimer > 0f)
        {
            _flashTimer -= Time.deltaTime;
            if (_flashTimer <= 0f && _spriteRenderer != null)
            {
                Debug.Log($"[Flash END] t={Time.time:F3} → revert to {_originalColor}");
                _spriteRenderer.color = _originalColor;
            }
        }

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

    // 매 물리 프레임의 메인:
    //   - 잡힌 상태면 그랩 위치로 강제 이동
    //   - 그 외엔 피격 위치 보정 진행 + 분리력 적용
    //   - 벽 반사 계산을 위해 직전 velocity 기록
    private void FixedUpdate()
    {
        if (_rb != null)
        {
            if (_isGrabbed)
            {
                // 잡힌 동안엔 자체 물리 무시하고 플레이어가 지정한 위치로 강제 이동.
                _rb.linearVelocity = Vector2.zero;
                _rb.MovePosition(_grabTargetPosition);
                _lastVelocity = Vector2.zero;
                return;
            }

            ApplySmoothHitPositionCorrection();
            ApplySeparation();
            _lastVelocity = _rb.linearVelocity;
        }
    }

    // 같은 레이어의 다른 적과 너무 가까우면 옆으로 밀어 시각적으로 분리.
    // 알고리즘:
    //   1) OverlapCircle로 주변 적 검색
    //   2) 각 적마다 거리 비례로 push 벡터 누적 (가까울수록 강하게)
    //   3) 평균 방향 × Strength × deltaTime 만큼 X축으로만 밀어냄
    //      (Y로 밀면 공중 부양/점프 효과 생겨서 어색함)
    private void ApplySeparation()
    {
        if (_separationRadius <= 0f || _separationStrength <= 0f) return;
        if (_separationLayer.value == 0) return;

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

        int count = Physics2D.OverlapCircle(_rb.position, _separationRadius, filter, _separationBuffer);
        Vector2 push = Vector2.zero;
        int contributors = 0;

        for (int i = 0; i < count; i++)
        {
            Collider2D other = _separationBuffer[i];
            if (other == null) continue;
            if (other.attachedRigidbody == _rb) continue;  // 자기 자신 스킵

            Vector2 away = _rb.position - (Vector2)other.transform.position;
            float dist = away.magnitude;
            if (dist >= _separationRadius) continue;       // 영향권 밖

            Vector2 dir;
            if (dist < 0.001f)
            {
                // 완전히 같은 위치인 경우 무작위 수평 방향으로 분리
                dir = UnityEngine.Random.value < 0.5f ? Vector2.left : Vector2.right;
            }
            else
            {
                dir = away / dist;
            }

            // 가까울수록 strength가 1에 가까워짐 (멀어질수록 0).
            float strength = 1f - (dist / _separationRadius);
            push += dir * strength;
            contributors++;
        }

        if (contributors == 0) return;

        push /= contributors;
        // X축으로만 분리. Y는 중력에 맡겨서 바운스/공중부양 방지.
        _rb.position += new Vector2(push.x, 0f) * (_separationStrength * Time.fixedDeltaTime);
    }

    // IDamageable 구현. 데미지 처리 흐름:
    //   1) 죽었으면 무시
    //   2) 잡힌 상태 해제 (피격되면 잡기 풀림)
    //   3) 시각 효과 (빨간 깜빡)
    //   4) 피격 애니메이션 재생
    //   5) 이전 넉백 속도 초기화 후 새 넉백 적용
    //   6) 위치 보정 (옵션)
    //   7) Health.TakeDamage로 HP 감소 → 0이면 OnDied 이벤트로 HandleDeath 트리거
    public void TakeDamage(int amount, Vector2 hitVelocity = default, string hitReactionAnimationState = null, Vector2? hitTargetPosition = null, bool correctHitTargetY = false, int hitPositionSolidMask = 0, float hitPositionCorrectionDuration = 0f)
    {
        if (_health == null || _health.IsDead) return;

        _isGrabbed = false;

        if (_spriteRenderer != null)
        {
            Debug.Log($"[Flash START] t={Time.time:F3} duration={_hitFlashDuration:F3} (current color was {_spriteRenderer.color})");
            _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;
            }
        }

        // 시각/반응 처리가 끝난 뒤 HP를 감산해서 OnDied 이벤트가 마지막에 발화되게 한다.
        _health.TakeDamage(amount);
        Debug.Log($"{name} 피격: -{amount} (HP: {_health.CurrentHealth}/{_health.MaxHealth})");
    }

    // 플레이어가 잡기 액션 시작 시 호출. 이 적이 잡힘 상태로 진입.
    // 이후 매 FixedUpdate에서 _grabTargetPosition으로 강제 이동됨.
    public void BeginGrab(string grabbedAnimationState, int solidMask)
    {
        if (_health == null || _health.IsDead) return;

        _isGrabbed = true;
        _grabSolidMask = solidMask;
        _isHitPositionCorrecting = false;
        _hitReactionTimer = 0f;
        _lastVelocity = Vector2.zero;

        if (_rb != null)
        {
            _rb.linearVelocity = Vector2.zero;
            _grabTargetPosition = _rb.position;
        }

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

    // 플레이어가 잡힌 적을 매 프레임 이동시킬 때 호출 (예: 들어올렸다 내려치기).
    // GetSafeHitTargetPosition으로 벽에 끼이지 않게 안전 위치 계산.
    public void UpdateGrabPosition(Vector2 position)
    {
        if (!_isGrabbed || _rb == null) return;

        _rb.linearVelocity = Vector2.zero;
        _grabTargetPosition = GetSafeHitTargetPosition(position, _grabSolidMask);
    }

    // 잡기 종료. 적은 다시 자체 물리로 돌아감.
    public void EndGrab()
    {
        _isGrabbed = false;
    }

    // Unity의 OnCollisionEnter2D 콜백. 두 가지 일을 처리:
    //   1) 지면 상태 갱신 (착지 감지)
    //   2) 넉백 중에 벽 부딪히면 반사 (튀어오르는 효과)
    // 플레이어와 부딪히면 반사 안 함 (다른 적과 부딪히는 경우만).
    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;
    }

    // 공중 피격은 항상 위로 띄우는 효과 적용 (격투게임 표준).
    // 지상 피격은 hitVelocity 그대로 사용.
    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;
    }

    // 매 FixedUpdate에서 진행 중인 위치 보정 보간 한 스텝 실행.
    // SmoothStep으로 부드러운 진입/이탈 곡선 사용.
    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;
    }

    // 목표 위치까지 cast해서 벽에 막히지 않는 최대 거리까지의 위치 반환.
    // 위치 보정/잡기 이동 시 적이 벽에 끼이는 걸 방지.
    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;  // skin만큼 떨어진 지점까지
    }

    // 모든 body collider를 direction 방향으로 cast해서 가장 가까운 hit 거리 반환.
    // GetSafeHitTargetPosition의 헬퍼.
    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;
    }

    // 접촉 노멀의 Y가 0.5보다 크면 (위쪽 방향이면) 지면 위로 판정.
    // 비스듬한 경사도 어느정도 지면으로 인정.
    private void UpdateGroundedState(Collision2D collision)
    {
        for (int i = 0; i < collision.contactCount; i++)
        {
            if (collision.GetContact(i).normal.y > 0.5f)
            {
                _isGrounded = true;
                return;
            }
        }
    }

    // 피격 후 벽에 부딪힐 때 속도를 반사 (Vector2.Reflect 사용).
    // _lastVelocity와 현재 velocity 중 더 큰 것을 사용하는 이유:
    //   - 충돌 직전 frame에 이미 velocity가 줄어들 수 있어서, 직전 값을 fallback으로 사용
    // _hitReactionTimer를 다시 채워서 연쇄 반사 가능 (벽 사이를 통통 튀게).
    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;
        // 반사 후 Y가 너무 낮으면 위로 튀어오르게 강제 (지면에 깔리지 않도록).
        if (bouncedVelocity.y < _wallBounceUpwardVelocity)
            bouncedVelocity.y = _wallBounceUpwardVelocity;

        _rb.linearVelocity = bouncedVelocity;
        _hitReactionTimer = _hitReactionDuration;
    }

    // Health.OnDied 이벤트 콜백. 사망 처리.
    // _dropWeapon이 설정돼 있고 픽업 프리팹이 있으면 자기 위치에 무기 드랍.
    private void HandleDeath()
    {
        Debug.Log($"{name} 사망");
        DropWeaponIfAny();
        Destroy(gameObject);
    }

    private void DropWeaponIfAny()
    {
        if (_dropWeapon == null || _weaponPickupPrefab == null) return;

        WeaponPickup pickup = Instantiate(_weaponPickupPrefab, transform.position, Quaternion.identity);
        pickup.Initialize(_dropWeapon);
    }
}
