using System;
using System.Collections;
using UnityEngine;

[SelectionBase]
public class Battler : MonoBehaviour
{
    public event Action<int> PerformedAttack;
    public event Action<int, bool> TookHit;
    public event Action Won;
    public event Action Died;
    public static event Action<Battler> AnyJoinedBattle;
    public static event Action<Battler> AnyJoinedGame;
    public static event Action<Battler> AnyLeftBattle;
    public static event Action<Battler> AnyLeftGame;

    public Transform Head;
    [SerializeField] PlayerMover _playerMover;
    [SerializeField] CharacterModel _characterModel;
    [SerializeField] BattlerClassPresenter _classPresenter;
    [SerializeField] MeshRenderer _floorMarker;
    [SerializeField] Light _light;

    public bool IsReady => _playerData != null;
    public Color ClassColor => _playerData?.Profession?.DisplayColor ?? Color.cyan;
    public Color SpeciesColor => _playerData?.Species?.DisplayColor?? Color.cyan;

    float _walkProgress;
    Transform _fight, _start;
    float _healCountdown;
    PlayerData _playerData;
    BattlerStats _battlerStatChanges => _speciesStats + _classStats;

    BattlerStats _speciesStats => _playerData.Species.GetBattlerStatChanges(_wins);
    BattlerStats _classStats => _playerData.Profession.BattlerStatChanges;

    public event Action<PlayerData> PlayerChanged;
    public event Action<int, int> HealthChanged;


    public int MaxHealth => BattleManager.Settings.MaxHealth + _battlerStatChanges.LifeMax;
    public int Health => _playerData.Health;
    public string PlayerName => _playerData.PlayerName;
    public Sprite HeadshotSprite { get; private set; }
    public Profession Profession => _playerData.Profession;
    public Species Species => _playerData.Species;


    void Awake()
    {
        _floorMarker.material.color = new Color(ClassColor.r, ClassColor.g, ClassColor.b, 0.2f);
        _light.color = new Color(ClassColor.r, ClassColor.g, ClassColor.b, 0.2f);
      //  GetComponentInChildren<CharacterHealthBar>(true).gameObject.SetActive(false);
    }

    void Start()
    {
        if (_playerData == null || _playerData.Species == null)
        {
            Debug.LogError($"Null Data on {gameObject.name}");
            return;
        }
        AnyJoinedGame?.Invoke(this);
    }

    void OnValidate()
    {
        _characterModel = GetComponent<CharacterModel>();
        _playerMover = GetComponent<PlayerMover>();
    }

    public void Initialize(Transform start, Transform fight)
    {
        _playerMover.SetDestination(start);
        _start = start;
        _fight = fight;
        AnyJoinedBattle?.Invoke(this);
    }

    void Update()
    {
        var state = BattleManager.Instance.BattleState;

        RegenerateHealth(state);

        if (!_start || !_fight || _walkProgress > 1f)
            return;

        if (state != BattleState.Fighting && state != BattleState.MovingTogether)
        {
            _playerMover.SetDestination(_start);
            return;
        }

        _playerMover.SetDestination(_fight);
        _walkProgress += Time.deltaTime;
    }

    public int GetHitModifier()
    {
        return _playerData.Pledge?.BattlerStatChanges.HitChance ?? 0 +
            _playerData.Profession.BattlerStatChanges.HitChance +
            SpeciesAndClassCounter.GetHitModifier(this);
    }

    void RegenerateHealth(BattleState state)
    {
        if (_playerData.Health <= 0 || _playerData.Health >= MaxHealth)
            return;

        var canRegenerate = state == BattleState.Fighting || state == BattleState.FightOverResults;
        if (!canRegenerate)
            return;

        _healCountdown -= Time.deltaTime;
        if (_healCountdown > 0)
            return;

        var amountToHeal =state == BattleState.FightOverResults
            ? Mathf.Max(0, BattleManager.Settings.RegenOutOfCombat + _battlerStatChanges.LifeRegenOutOfCombat)
            : Mathf.Max(0, BattleManager.Settings.RegenInCombat + _battlerStatChanges.LifeRegenInCombat);

        Heal(amountToHeal);

        _healCountdown = 1f;
    }

    void Heal(int amountToHeal)
    {
        _playerData.Health += amountToHeal;

        if (_playerData.Health > MaxHealth)
            _playerData.Health = MaxHealth;
        HealthChanged?.Invoke(_playerData.Health, amountToHeal);
    }

    public void ResetBattler()
    {
        transform.localScale = Vector3.one;
        HealToMaxHealth();

        _walkProgress = 1;
        gameObject.SetActive(true);
    }

    void HealToMaxHealth() => _playerData.Health = MaxHealth;

    public void TakeHit((int, bool) hitData)
    {
        StartCoroutine(TakeHitDelayed(hitData.Item1, hitData.Item2));
    }

    IEnumerator TakeHitDelayed(int amount, bool wasCrit)
    {
        yield return new WaitForSeconds(BattleManager.Settings.DelayBetweenAttackAndHitImpact);

        var damagePercentageToTakeInt = 100 - _battlerStatChanges.DamageMitigation;
        var incomingDamageMultiplier = damagePercentageToTakeInt / 100f;
        var amountOfDamageToActuallyTake = Mathf.RoundToInt(amount * incomingDamageMultiplier);

        SetHealth(Health - amountOfDamageToActuallyTake, -amountOfDamageToActuallyTake);

        if (Health > 0)
            TookHit?.Invoke(amountOfDamageToActuallyTake, wasCrit);
        else
            StartCoroutine(KillAndRemove());
    }

    IEnumerator KillAndRemove()
    {
        ActionLogText.Add(PlayerName,BattleManager.Settings.DeathMessages.ChooseRandom());
        Died?.Invoke();
        AnyLeftBattle?.Invoke(this);
        _playerMover.Stop();
        yield return new WaitForSeconds(BattleManager.Settings.DeadBodyRemainsDuration);
        gameObject.SetActive(false);
        AnyLeftGame?.Invoke(this);
    }

    public (int, bool) Attack(int roll)
    {
        var criticalStrikeChance = 10 + _battlerStatChanges.CriticalAttackChance;
        var rollNeededToCriticallyStrike = 100 - criticalStrikeChance;

        var damage = BattleManager.Settings.NormalDamage + _battlerStatChanges.AttackDamage;
        
        bool wasCrit = roll > rollNeededToCriticallyStrike;
        if (wasCrit)
            damage += _battlerStatChanges.CriticalAttackDamage;

        PerformedAttack?.Invoke(damage);
        
        if (_battlerStatChanges.HealOnHit > 0)
            Heal(_battlerStatChanges.HealOnHit);
        
        return (damage, wasCrit);
    }

    void SetHealth(int value, int change)
    {
        _playerData.Health = value;
        HealthChanged?.Invoke(value, change);
    }

    public void SetWin()
    {
        Won?.Invoke();
        AnyLeftBattle?.Invoke(this);
        transform.LookAt(Camera.main.transform);
        _wins++;
        _characterModel.ShowModelForPlayer(PlayerName);
        //transform.localScale = Vector3.one * 1.5f;
    }


    public void BindToData(PlayerData playerData)
    {
        _playerData = playerData;
        _playerData.Battler = this;

        UpdatePlayerSpecies();
        UpdatePlayerClass();

        PlayerChanged?.Invoke(playerData);
    }

    public void UpdatePlayerClass()
    {
        _playerData.EnsurePlayerHasProfession();

        if (_classPresenter)
            _classPresenter.SetClass(_playerData.Profession);

        _playerData.EnsurePlayerHasPledge();
    }

    public void UpdatePlayerSpecies()
    {
        _playerData.EnsurePlayerHasSpecies();
        _characterModel.ShowModelForPlayer(PlayerName);
    }

    public void ClearFightData()
    {
        _fight = null;
        _start = null;
    }

    public void SetDestination(Transform destination) => _playerMover.SetDestination(destination);
    public void PrepareForNewBattle() => _walkProgress = 0f;

    int _wins = 0;
    public string GetModelName() => Species.GetModelName(_wins);
}