// WingsEmu
// 
// Developed by NosWings Team

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using PhoenixLib.Events;
using WingsEmu.Core.Extensions;
using WingsEmu.DTOs.BCards;
using WingsEmu.DTOs.Skills;
using WingsEmu.Game;
using WingsEmu.Game._enum;
using WingsEmu.Game.Algorithm;
using WingsEmu.Game.Battle;
using WingsEmu.Game.Buffs;
using WingsEmu.Game.Characters;
using WingsEmu.Game.Entities;
using WingsEmu.Game.Extensions;
using WingsEmu.Game.Helpers.Damages;
using WingsEmu.Game.Mates;
using WingsEmu.Packets.Enums;
using WingsEmu.Packets.Enums.Battle;

namespace WingsEmu.Plugins.BasicImplementations.Event.Battle;

public class ProcessHitEventHandler : IAsyncEventProcessor<ProcessHitEvent>
{
    private static readonly byte MAX_TARGETS = 50;
    private readonly IBattleEntityDumpFactory _battleEntityDumpFactory;
    private readonly IBCardEffectHandlerContainer _bCardEffectHandler;
    private readonly IDamageAlgorithm _damageAlgorithm;
    private readonly IRandomGenerator _randomGenerator;
    private readonly ISkillUsageManager _skillUsageManager;

    public ProcessHitEventHandler(IBattleEntityDumpFactory battleEntityDumpFactory, IDamageAlgorithm damageAlgorithm,
        ISkillUsageManager skillUsageManager, IBCardEffectHandlerContainer bCardEffectHandler, IRandomGenerator randomGenerator)
    {
        _battleEntityDumpFactory = battleEntityDumpFactory;
        _damageAlgorithm = damageAlgorithm;
        _skillUsageManager = skillUsageManager;
        _bCardEffectHandler = bCardEffectHandler;
        _randomGenerator = randomGenerator;
    }

    public async Task HandleAsync(ProcessHitEvent e, CancellationToken cancellation)
    {
        IBattleEntity caster = e.Caster;

        if (!caster.IsAlive())
        {
            caster.CancelCastingSkill();
            return;
        }

        IBattleEntity target = e.Target;
        SkillInfo skill = e.HitInformation.Skill;
        Position position = e.HitInformation.Position;

        BCardDTO[] afterAttackAllAllies = skill.BCardsType.TryGetValue(SkillCastType.AFTER_ATTACK_ALL_ALLIES, out HashSet<BCardDTO> allAllies) ? allAllies.ToArray() : Array.Empty<BCardDTO>();
        BCardDTO[] beforeAttackOnMainTarget = skill.BCardsType.TryGetValue(SkillCastType.BEFORE_ATTACK_ON_MAIN_TARGET, out HashSet<BCardDTO> beforeAttackMain)
            ? beforeAttackMain.ToArray()
            : Array.Empty<BCardDTO>();
        BCardDTO[] beforeAttackSelf = skill.BCardsType.TryGetValue(SkillCastType.BEFORE_ATTACK_SELF, out HashSet<BCardDTO> beforeAttack) ? beforeAttack.ToArray() : Array.Empty<BCardDTO>();
        BCardDTO[] beforeAttackAllTargets = skill.BCardsType.TryGetValue(SkillCastType.BEFORE_ATTACK_ALL_TARGETS, out HashSet<BCardDTO> beforeAttackAll)
            ? beforeAttackAll.ToArray()
            : Array.Empty<BCardDTO>();

        IBattleEntityDump attacker = caster switch
        {
            IPlayerEntity character => _battleEntityDumpFactory.Dump(character, skill),
            IMonsterEntity monster => _battleEntityDumpFactory.Dump(monster, skill),
            INpcEntity mapNpc => _battleEntityDumpFactory.Dump(mapNpc, skill),
            IMateEntity mate => _battleEntityDumpFactory.Dump(mate, skill),
            _ => null
        };

        if (attacker == null)
        {
            caster.CancelCastingSkill();
            return;
        }

        List<IBattleEntity> entities;

        TargetHitType hitType = skill.HitType;

        (int randomChance, int range) =
            caster.BCardComponent.GetAllBCardsInformation(BCardType.SpecialDamageAndExplosions, (byte)AdditionalTypes.SpecialDamageAndExplosions.SurroundingDamage, caster.Level);

        if (randomChance != 0 && _randomGenerator.RandomNumber() <= randomChance && hitType is not TargetHitType.SpecialArea)
        {
            hitType = TargetHitType.EnemiesInAffectedAoE;
            skill.AoERange += (byte)range;
        }

        if (skill.TargetType == TargetType.NonTarget)
        {
            entities = position.GetEnemiesInRange(caster, skill.AoERange).ToList();
            if (skill.BCards.Any(x => (BCardType)x.Type == BCardType.FalconSkill && x.SubType == (byte)AdditionalTypes.FalconSkill.FalconFocusLowestHP))
            {
                if (entities.Count != 0)
                {
                    entities = Lists.Create(entities.OrderBy(x => x.HpPercentage).First());
                }
            }
        }
        else
        {
            switch (hitType)
            {
                case TargetHitType.EnemiesInAffectedAoE:
                    entities = skill.Vnum == (short)SkillsVnums.DOUBLE_RIPPER ? position.GetEnemiesInRange(caster, skill.AoERange).ToList() : target.GetEnemiesInRange(caster, skill.AoERange).ToList();

                    break;
                case TargetHitType.SpecialArea:
                    entities = _skillUsageManager.GetMultiTargets(caster.Id)
                        .Select(x => caster.MapInstance.GetBattleEntity(x.Item1, x.Item2))
                        .Where(x => x != null && caster.Position.IsInAoeZone(x.Position, 10) && caster.IsEnemyWith(x))
                        .ToList();
                    _skillUsageManager.ResetMultiTargets(caster.Id);
                    break;
                case TargetHitType.TargetOnly:
                    entities = Lists.Create(target);
                    break;
                default:
                    caster.CancelCastingSkill();
                    return;
            }

            if (!caster.Equals(target))
            {
                entities.SetFirst(target);
            }
        }

        if (!caster.IsPlayer() || caster is IPlayerEntity playerEntity && playerEntity.CheatComponent.HasNoTargetLimit == false)
        {
            entities = entities.Take(MAX_TARGETS).ToList();
        }

        foreach (BCardDTO bCard in beforeAttackOnMainTarget)
        {
            _bCardEffectHandler.Execute(target, caster, bCard, skill, position);
        }

        foreach (BCardDTO bCard in beforeAttackSelf)
        {
            _bCardEffectHandler.Execute(caster, caster, bCard, skill, position);
        }

        var targets = new List<(IBattleEntity, DamageAlgorithmResult)>();
        foreach (IBattleEntity entity in entities)
        {
            if (caster is IPlayerEntity c)
            {
                if (c.CheatComponent.IsInvisible)
                {
                    continue;
                }

                if (entity.IsMonster())
                {
                    var monster = entity as IMonsterEntity;
                    sbyte monsterMinRange = monster.MinimumAttackRange;
                    if (monsterMinRange != 0 && skill.Range < monsterMinRange)
                    {
                        continue;
                    }
                }
            }

            foreach (BCardDTO bCard in beforeAttackAllTargets)
            {
                _bCardEffectHandler.Execute(entity, caster, bCard, skill, position);
            }

            IBattleEntityDump defender = entity switch
            {
                IPlayerEntity character => _battleEntityDumpFactory.Dump(character, skill, true, entity.IsSameEntity(target)),
                IMonsterEntity monster => _battleEntityDumpFactory.Dump(monster, skill, true, entity.IsSameEntity(target)),
                INpcEntity mapNpc => _battleEntityDumpFactory.Dump(mapNpc, skill, true, entity.IsSameEntity(target)),
                IMateEntity mate => _battleEntityDumpFactory.Dump(mate, skill, true, entity.IsSameEntity(target)),
                _ => null
            };

            if (defender == null)
            {
                caster.CancelCastingSkill();
                continue;
            }

            DamageAlgorithmResult algorithmResult = _damageAlgorithm.GenerateDamage(attacker, defender, skill);
            targets.Add((entity, algorithmResult));

            if (caster.IsEnemyWith(entity))
            {
                continue;
            }

            foreach (BCardDTO bCard in afterAttackAllAllies)
            {
                _bCardEffectHandler.Execute(entity, caster, bCard, skill, position);
            }
        }

        foreach (BCardDTO bCard in afterAttackAllAllies)
        {
            _bCardEffectHandler.Execute(caster, caster, bCard, skill, position);
        }

        caster.MapInstance.AddHitRequest(new HitRequest(targets, e.HitInformation, target));
    }
}