using UnityEngine;
using System;
using System.Collections;
using UnityEngine.InputSystem;
using UnityEngine.UI;

public enum CombatState { Idle, Punch, Slide, Kick, Block }

public class PlayerCombat : MonoBehaviour
{
    // Utility
    [SerializeField] private Animator animator;
    [SerializeField] private BoxCollider2D combatCollider;
    [SerializeField] private BeatClock beatClock;
    [SerializeField] private String nextLevelName;
    [HideInInspector] public (CombatMove, CombatState) inputBufferMove;
    private float inputBufferTime;
    private Rigidbody2D rb;
    public event Action OnScoreIncreased;
    public int enemiesToDefeat;
    public float levelLoadingDelay = 1.5f;
    // Combat Configuration
    [SerializeField] private Transform attackPoint;
    [SerializeField] private int damage = 10;
    [SerializeField] private LayerMask enemyMask;
    [SerializeField] private float attackRadius = 1f;
    [SerializeField] private float inputBufferThreshold = 0.5f;
    //Beat Configuration
    [SerializeField] private float goodMultiplier = 1.5f;
    [SerializeField] private float perfectMultiplier = 2f;
    [SerializeField] private Image beatImage;
    [SerializeField] private Sprite perfectBeat;
    [SerializeField] private Sprite goodBeat;
    [SerializeField] private Sprite missBeat;
    [SerializeField] private Sprite transparentBeat;
    
    private BeatClock.BeatResult _pendingBeatResult = BeatClock.BeatResult.Miss;
    private bool _pendingBeatValid = false;
    
    // Combat Moves
    public CombatMove punchMove;
    public CombatMove punchMove1;
    public CombatMove punchMove2;
    public CombatMove swingMove;
    public CombatMove slideMove;
    public CombatMove kickMove;
    public CombatMove blockMove;
    
    // State
    [HideInInspector] public CombatState state = CombatState.Idle;
    private int punchCombo = 0;
    
    // Communication with Player
    private PlayerMovement playerMovementScript;
    
    [Header("Input Actions")]
    public InputActionReference punchAction;
    public InputActionReference blockAction;
    public InputActionReference kickAction;
    public InputActionReference slideAction;
    
    private void OnEnable() 
    {
        punchAction.action.Enable();
        blockAction.action.Enable();
        kickAction.action.Enable();
        slideAction.action.Enable();
    }
    
    void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        state = CombatState.Idle;
        playerMovementScript = GetComponent<PlayerMovement>();
    }

    void Update()
    {
        switch (state)
        {
            case CombatState.Punch:
                HandlePunching();
                break;
            case CombatState.Slide:
                HandleSliding();
                break;
            case CombatState.Kick:
                HandleKicking();
                break;
            case CombatState.Block:
                HandleBlocking();
                break;
            default:
                HandleIdle();
                break;
        }
    }
    
    /// <summary>
    /// Being in the idle states means the player is free to instantly change to another state without disruptions.
    /// To enter the idle state, there must be no input queued up in the inputBuffer.
    /// </summary>
    private void HandleIdle()
    {
        // player is allowed to move if just idling in combat
        playerMovementScript.canMove = true;
        
        // reset the punch combo
        punchCombo = 0;
        
        // start performing move if move was inputted
        if (TryGetInput(out CombatMove move, out CombatState newState))
        {
            StartMove(move, newState);
        }
    }

    private void HandlePunching()
    {
        // player is not allowed to move if performing a punch animation
        playerMovementScript.canMove = false;
        
        // buffer the input so it can be performed in the future
        if (TryGetInput(out CombatMove move, out CombatState newState))
        {
            // if chained punches, change punch state
            if (newState == CombatState.Punch)
            {
                if (punchCombo == 1) move = punchMove1;
                if (punchCombo == 2) move = punchMove2;
                // if this was your 3rd punch, don't chain further
                if (punchCombo == 3) return;
            }
            
            // if you kicked after your 2nd punch, do a swing
            if (newState == CombatState.Kick && punchCombo == 2)
            {
                punchCombo = 0;
                move = swingMove;
            }
            
            inputBufferMove = (move, newState);
            inputBufferTime = Time.time;
            
        }
    }
    
    private void HandleSliding()
    {
        // notify player movement to slow down due to friction with the floor
        playerMovementScript.isSliding = true;
        
        // buffer the input so it can be performed in the future
        if (TryGetInput(out CombatMove move, out CombatState newState))
        {
            // you can't slide again after sliding once without gaining more momentum
            if (move == slideMove) return;
            inputBufferMove = (move, newState);
            inputBufferTime = Time.time;
        }
    }
    
    private void HandleKicking()
    {
        // player is not allowed to move if performing a kick animation
        playerMovementScript.canMove = false;
        
        // buffer the input so it can be performed in the future
        if (TryGetInput(out CombatMove move, out CombatState newState))
        {
            inputBufferMove = (move, newState);
            inputBufferTime = Time.time;
        }
    }

    private void HandleBlocking()
    {
        // player is not allowed to move if performing a block
        playerMovementScript.canMove = false;
        
        // As long as the player holds the block key → remain blocking
        if (blockAction.action.IsPressed())
        {
            gameObject.layer = LayerMask.NameToLayer("Block");
            return;
        }

        // If the key is released → exit block
        animator.Play("idle");
        gameObject.layer = LayerMask.NameToLayer("Player");
        state = CombatState.Idle;
    }
    
    /// Returns 'true' if valid input was found, otherwise returns 'false'
    /// The output data is put inside the 'out' parameters
    private bool TryGetInput (out CombatMove move, out CombatState newState)
    {
        move = null;
        newState = CombatState.Idle;
        
        // Punch
        if (punchAction.action.WasPressedThisFrame())
        {
            StampBeatJudgementForThisAttack();
            move = punchMove;
            newState = CombatState.Punch;
            return true;
        }
        
        // Slide
        // do not allow sliding if player has no momentum
        if (slideAction.action.WasPressedThisFrame() && rb.linearVelocity != Vector2.zero)
        {
            StampBeatJudgementForThisAttack();
            move = slideMove;
            newState = CombatState.Slide;
            return true;
        }
        
        // Kick
        if (kickAction.action.WasPressedThisFrame())
        {
            StampBeatJudgementForThisAttack();
            move = kickMove;
            newState = CombatState.Kick;
            return true;
        }
        
        // Block
        if (blockAction.action.IsPressed())
        {
            move = blockMove;
            newState = CombatState.Block;
            return true;
        }

        return false;
    }

    void StampBeatJudgementForThisAttack()
    {
        if (beatClock == null || !beatClock.IsPlaying)
        {
            _pendingBeatValid = false;
            _pendingBeatResult = BeatClock.BeatResult.Miss;
            return;
        }
        var result = beatClock.JudgeNow(out double signedErrorSec);
        if (result != BeatClock.BeatResult.Miss)
        {
            _pendingBeatValid = true;
            _pendingBeatResult = result;
        }
    }
    /// Starts the animation for the specified move and sets the new combat state.
    void StartMove(CombatMove move, CombatState newState)
    {
        // increase punch chain combo
        if (newState == CombatState.Punch) punchCombo++;
        
        state = newState;
        animator.Play(move.animation.name, 0, 0f);
    }

    public void DealDamage()
    {
        Collider2D[] hits = Physics2D.OverlapCircleAll(
            attackPoint.position,
            attackRadius,
            enemyMask
        );
        
        // Compute multiplier once per swing
        float multiplier = 1f;
        if (_pendingBeatValid)
        {
            if (_pendingBeatResult == BeatClock.BeatResult.Perfect) multiplier = perfectMultiplier;
            else if (_pendingBeatResult == BeatClock.BeatResult.Good) multiplier = goodMultiplier;
        }
        
        
        foreach (var hit in hits)
        {
            Health enemyHealth = hit.GetComponent<Health>();
            
            //stun the enemy
            if (hit.TryGetComponent(out EnemyCombat combat))
            {
                combat = hit.GetComponent<EnemyCombat>();
                if (!combat.IsStunned)
                {
                    combat.GetStunned();
                }
            }
            
            if (enemyHealth != null)
            {
                int finalDamage = Mathf.RoundToInt(damage * multiplier);
                enemyHealth.TakeDamage(finalDamage);

                if(beatImage != null){ChangeBeatImage(_pendingBeatResult);}
                // Consume beat crit ONLY if we actually dealt damage
                _pendingBeatValid = false;
                _pendingBeatResult = BeatClock.BeatResult.Miss;
                
                OnScoreIncreased?.Invoke();
                SpawnVFX(hit);
                return;
            }
            
        }
    }

    void ChangeBeatImage(BeatClock.BeatResult result)
    {
        switch (result)
        {
            case BeatClock.BeatResult.Miss:
                beatImage.sprite = missBeat;
                break;
            case BeatClock.BeatResult.Good:
                beatImage.sprite = goodBeat;
                break;
            case BeatClock.BeatResult.Perfect:
                beatImage.sprite = perfectBeat;
                break;
        }
        StartCoroutine(MakeBeatImageDisappear());
    }

    IEnumerator MakeBeatImageDisappear()
    {
        yield return new WaitForSeconds(1f);
        beatImage.sprite = transparentBeat;
    }
    public void EndAttack()
    {
        
        // you are no longer sliding
        if (state == CombatState.Slide) playerMovementScript.endSlide();
        
        // check if we can chain the next move
        if (inputBufferMove.Item2 != CombatState.Idle && Time.time - inputBufferTime <= inputBufferThreshold)
        {
            StartMove(inputBufferMove.Item1, inputBufferMove.Item2);
            inputBufferMove = (null, CombatState.Idle);
            return;
        }
        
        animator.Play("idle");
        state = CombatState.Idle;
    }

    public void OverrideHitboxLogic(bool startSlide)
    {
        if (startSlide)
        {
            combatCollider.includeLayers = LayerMask.GetMask("Enemies");
            combatCollider.excludeLayers = LayerMask.GetMask("Stage", "Projectile");
        }
        else
        {
            combatCollider.includeLayers = LayerMask.GetMask("Enemies", "Projectile");
            combatCollider.excludeLayers = LayerMask.GetMask("Stage");
        }
    }

    public void OnEnemyDefeat()
    {
        enemiesToDefeat--;

        if (enemiesToDefeat <= 0)
            StartCoroutine(LoadLevelWithDelay());

    }

    IEnumerator LoadLevelWithDelay()
    {
        yield return new WaitForSeconds(levelLoadingDelay);
        GameManager.Instance.LoadLevel(nextLevelName);
    }
    private void SpawnVFX(Collider2D hit)
    {
        if (HitEffectManager.Instance != null)
        {
            Vector2 hitPos = hit.bounds.center;
            Vector2 dir = (hitPos - (Vector2)attackPoint.position).normalized;
            
            HitEffectManager.Instance.Spawn(
                ReturnRandomHitEffectType(UnityEngine.Random.Range(0, 3)),
                hitPos,
                dir
            );
        }
    }
    private HitEffectType ReturnRandomHitEffectType(int randomNumber)
    {
        HitEffectType type = HitEffectType.SmackHit;
        if (randomNumber == 0) type = HitEffectType.ZapHit;
        else if (randomNumber == 1) type = HitEffectType.SmackHit;
        else if (randomNumber == 2) type = HitEffectType.SplashHit;

        return type;
    }
    
    private void OnDrawGizmos()
    {
        if (attackPoint == null) return;

        Gizmos.color = Color.yellow;
        Gizmos.DrawWireSphere(attackPoint.position, attackRadius);

        // Show which objects would be hit right now
        Collider2D[] hits = Physics2D.OverlapCircleAll(attackPoint.position, attackRadius, enemyMask);
        Gizmos.color = Color.red;

        foreach (var h in hits)
        {
            Gizmos.DrawSphere(h.transform.position, 0.05f);
        }
    }
}
