using System;
using System.Collections;
using System.Data;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using UnityEditor;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using static UnityEditor.PlayerSettings;
using static UnityEngine.EventSystems.EventTrigger;

[SelectionBase]
public class Player : MonoBehaviour {
  public static Player Instance { get; private set; }

  [Header("Asset/Prefab")]
  [SerializeField] public BuilderManager Builder;

  [Header("References")]
  [SerializeField] public Rigidbody2D Rigidbody;
  [SerializeField] public Animator Animator;
  [SerializeField] public SpriteRenderer Renderer;
  [SerializeField] public Material MaterialColorOverlay;

  [Header("Character Class")]
  [SerializeField] private ParticleSystem Aura;
  [SerializeField] private GameObject[] ClassIndicators;
  private ClassBase ActiveClass;
  [SerializeField] private MeleeFighterClass FighterClass;
  public MeleeFighterAttackAnimator AttackAnimator;

  [Header("Stamina")]
  [SerializeField] public float StaminaMax = 100;
  [SerializeField] public float Stamina = 0;
  [SerializeField] public float StaminaRegenPerSecond = 5;
  [SerializeField] public Slider StaminaSliderHud;


  [Header("Movement Attributes")]
  [SerializeField] public float MoveSpeed = 8;
  [SerializeField] public float MoveSpeedDampener = 1;
  [SerializeField] private float DashSpeedInitial = 200;
  [SerializeField] private float DashSpeed = 0;
  [SerializeField] private float DashDecayRate = 30;
  [SerializeField] private float DashDelay1 = 0.05f;
  [SerializeField] private float DashDelay2 = 0.05f;
  [SerializeField] private float DashCooldown = 0.1f;
  //[SerializeField] private float DriftSpeed = 60;
  //[SerializeField] private float DriftFactorial = 0.85f;
  [SerializeField] private float JumpDelay = 0.3f;

  [Header("VFX")]
  [SerializeField] private GameObject VfxDash;
  private VfxHandlerBase VfxDashHandler;
  [SerializeField] private GameObject VfxKineticSurge;
  [HideInInspector] public VfxHandlerBase VfxKineticSurgeHandler;
  [SerializeField] private GameObject VfxShockwave;
  [HideInInspector] public GameObjectPool VfxShockwavePool;
  [SerializeField] private GameObject VfxAirJump;
  [HideInInspector] public GameObjectPool VfxAirJumpPool;

  private BoxCollider2D[] BoxColliders;
  public bool LockMovement;

  [SerializeField] public PlayerCombatHandler Combat { get; private set; }


  public Vector2 PrevDirection = Vector2.zero;
  private Vector2 MoveDirection = Vector2.zero;
  private Vector2 DashDirection = Vector2.zero;

  public PlayerJumpHandler Jump;
  public bool IsDashing { get; private set; }
  public float DashTime { get; private set; }
  public bool CanDash { get; private set; } = true;

  public bool SkillInUse = false;
  private enum Directions { Left, Right, Up, Down }



  void Awake() {
    Instance = this;
    Jump = new PlayerJumpHandler(this);
    Builder = GetComponent<BuilderManager>();

    VfxDashHandler = new VfxHandlerBase(VfxDash, 5, 5);
    VfxKineticSurgeHandler = new VfxHandlerBase(VfxKineticSurge, 5, 5);
    FighterClass = new MeleeFighterClass(this);
    VfxShockwavePool = new GameObjectPool(VfxShockwave, 5, 5);
    VfxAirJumpPool = new GameObjectPool(VfxAirJump, 5, 5);
    BoxColliders = GetComponentsInChildren<BoxCollider2D>();
    StaminaSliderHud.maxValue = StaminaMax;
    SetClass(1);

    Combat = new PlayerCombatHandler();
  }


  public Action DoUpdate;
  private void Update() {
    KeyPressActions();
    GatherInput();
    UpdatePlayerStatus();
    UpdateActiveClass();
    DoUpdate?.Invoke();
  }

  private void GatherInput() {
    if (LockMovement) return;
    MoveDirection.x = Input.GetAxisRaw("Horizontal");
    MoveDirection.y = Input.GetAxisRaw("Vertical");
    MoveDirection = MoveDirection.NormalizedIso();
    if (MoveDirection.x != 0 || MoveDirection.y != 0)
      PrevDirection = MoveDirection;
  }

  private void KeyPressActions() {
    if (Input.GetKeyDown(KeyCode.LeftShift) || Input.GetKeyDown(KeyCode.RightShift)) {
      DoDash();
    } else if (Input.GetKeyDown(KeyCode.Space)) {
      Jump.Start();
    } else if (Input.GetKeyDown(KeyCode.F1)) {
      SetClass(0);
    } else if (Input.GetKeyDown(KeyCode.F2)) {
      SetClass(1);
    } else if (Input.GetKeyDown(KeyCode.F3)) {
      SetClass(2);
    } else if (Input.GetKeyDown(KeyCode.F4)) {
      SetClass(3);
    }

    if (Input.GetKeyDown(KeyCode.X)) ActiveClass.HandlePrimaryAttack();
    else if (Input.GetKeyDown(KeyCode.C)) ActiveClass.HandleSecondaryAttack();
  }

  private void DoDash() {
    if (!CanDash) return;
    if (!Jump.IsNonBlocking) return;
    if (LockMovement) return;
    if (Stamina < 25) return;
    Stamina -= 25;
    CanDash = false;
    IsDashing = true;
    DashDirection = MoveDirection;
    if (DashDirection == Vector2.zero) return;
    DashSpeed = DashSpeedInitial;
    if (Jump.Active) {
      LockMovement = true;
      DashSpeed /= 2;
    }
  }

  private void SetClass(int classIdx) {
    Builder.SetBuildMode(classIdx == 3);
    ActiveClass = FighterClass;

    foreach (var (indicator, i) in ClassIndicators.Select((obj, i) => (obj, i)))
      if (i != classIdx) {
        indicator.transform.localScale = Vector3.one;
        indicator.transform.rotation = Quaternion.identity;
      } else {
        indicator.transform.localScale = Vector3.one * 2;
        indicator.transform.rotation = Quaternion.AngleAxis(90, Vector3.forward);
      }

    RawImage image = ClassIndicators[classIdx].GetComponent<RawImage>();
    var main = Aura.main;
    main.startColor = image.color;
  }


  private void UpdatePlayerStatus() {
    if (Stamina == StaminaMax) return;
    Stamina = Mathf.Min(Stamina + StaminaRegenPerSecond * Time.deltaTime, StaminaMax);
    StaminaSliderHud.value = Stamina;
  }

  private void UpdateActiveClass() {
    if (ActiveClass == null) return;
    ActiveClass.Tick();

    if (ActiveClass.HitBoxDraw == null) return;
    if (!ActiveClass.HitBoxDraw.Active) return;
    Collider2D[] hits = Physics2D.OverlapCircleAll(ActiveClass.HitBoxDraw.Center, ActiveClass.HitBoxDraw.Radius);
    foreach (var hit in hits) {
      if (!hit.CompareTag("EnemyHurtBox")) continue;

      GameObject parent = hit.transform.parent?.gameObject;
      Vector2 knockbakDirection = ActiveClass.HitBoxDraw.Directional ? PrevDirection : parent.transform.position - transform.position;
      EnemySpawnerData.EnemyMap[parent].Damage(ActiveClass.HitBoxDraw.Damage, knockbakDirection, ActiveClass.HitBoxDraw.Knockback);
    }
  }







  private void FixedUpdate() {
    MovementUpdate();
    UpdateAnimation();
  }

  private void MovementUpdate() {
    if (Combat.TakingDamage) {
      Combat.ApplyKnockback();
      return;
    }

    var movement = (MoveDirection * MoveSpeed) / MoveSpeedDampener;
    if (IsDashing) {
      if (DashSpeed < (Jump.Active ? 0.4f : 0.2f)) {
        DashSpeed = 0;
        CanDash = true;
        IsDashing = false;
        LockMovement = false;
        Jump.Unpause();
      } else if (DashSpeed > (movement.magnitude * 2)) {
        Jump.Pause();
        movement = DashDirection * DashSpeed;
        if (Jump.Active)
          DashSpeed *= Mathf.Exp(-DashDecayRate * Time.deltaTime / 3);
        else
          DashSpeed *= Mathf.Exp(-DashDecayRate * Time.deltaTime);
      } else {
        movement = ((DashDirection * DashSpeed) + movement) / 2;
        DashSpeed *= Mathf.Exp(-DashDecayRate * Time.deltaTime);
      }
    }
    Rigidbody.linearVelocity = movement;
  }

  private void UpdateAnimation() {
    string state = GetAnimationState(MoveDirection);
    if (state == "") return;
    if (state.Last() == '_')
      state = state.Replace("_", "");
    else
      state += LastDirection.ToString();
    Animator.CrossFade(state, 0);
  }

  public enum Direction { Up, Down, Left, Right }
  public Direction LastDirection = Direction.Down;
  private string GetAnimationState(Vector2 input) {
    if (Mathf.Abs(input.x) > 0)
      LastDirection = (input.x > 0) ? Direction.Right : Direction.Left;
    else if (Mathf.Abs(input.y) > 0)
      LastDirection = (input.y > 0) ? Direction.Up : Direction.Down;

    if (SkillInUse) return ActiveClass.AnimationToPlay;
    if (!Jump.Active && input.sqrMagnitude < 0.01f) return "Idle";
    if (Jump.Active) return "Jump2";
    return "Run";
  }











  public static Action GizmoTick;
  void OnDrawGizmos() {
    DrawPlayerDirection();
    GizmoTick?.Invoke();
  }

  public static void DrawWireSphere(Color color, Vector3 center, float radius) {
    Gizmos.color = color;
    Gizmos.DrawWireSphere(center, radius);
  }

  private void DrawPlayerDirection() {
    if (Rigidbody == null) Rigidbody = GetComponent<Rigidbody2D>();
    if (Rigidbody == null) return;

    Gizmos.color = Color.cyan;

    Vector3 start = Rigidbody.transform.position;
    Vector3 end = start + (Vector3)(Rigidbody.linearVelocity * 1);

    Gizmos.DrawLine(start, end);
    DrawArrowHead(end, (end - start).normalized);
  }


  void DrawArrowHead(Vector3 position, Vector3 direction) {
    float arrowHeadAngle = 20f;
    float arrowHeadLength = 0.25f;

    Vector3 right = Quaternion.Euler(0, 0, arrowHeadAngle) * -direction;
    Vector3 left = Quaternion.Euler(0, 0, -arrowHeadAngle) * -direction;

    Gizmos.DrawLine(position, position + right * arrowHeadLength);
    Gizmos.DrawLine(position, position + left * arrowHeadLength);
  }
}
