//
//  SimpleAttackTechnique.cs
//
//  Copyright (c) František Boháček. All rights reserved.
//  Licensed under the MIT license. See LICENSE file in the project root for full license information.

using NosSmooth.Data.Abstractions.Enums;
using NosSmooth.Extensions.Combat.Errors;
using NosSmooth.Extensions.Combat.Extensions;
using NosSmooth.Extensions.Combat.Operations;
using NosSmooth.Extensions.Combat.Selectors;
using NosSmooth.Extensions.Pathfinding;
using NosSmooth.Game.Apis.Safe;
using NosSmooth.Game.Data.Entities;
using NosSmooth.Game.Errors;
using NosSmooth.Game.Extensions;
using Remora.Results;

namespace NosSmooth.Extensions.Combat.Techniques;

/// <summary>
/// A combat technique that will attack on the specified enemy, walk within range and use skill until the enemy is dead.
/// </summary>
public class SimpleAttackTechnique : ICombatTechnique
{
    private static OperationQueueType[] _handlingTypes = new[]
    {
        OperationQueueType.Item, OperationQueueType.TotalControl
    };

    private readonly long _targetId;
    private readonly NostaleSkillsApi _skillsApi;
    private readonly WalkManager _walkManager;
    private readonly ISkillSelector _skillSelector;
    private readonly IItemSelector _itemSelector;

    private ILivingEntity? _target;

    /// <summary>
    /// Initializes a new instance of the <see cref="SimpleAttackTechnique"/> class.
    /// </summary>
    /// <param name="targetId">The target entity id.</param>
    /// <param name="skillsApi">The skills api.</param>
    /// <param name="walkManager">The walk manager.</param>
    /// <param name="skillSelector">The skill selector.</param>
    /// <param name="itemSelector">The item selector.</param>
    public SimpleAttackTechnique
    (
        long targetId,
        NostaleSkillsApi skillsApi,
        WalkManager walkManager,
        ISkillSelector skillSelector,
        IItemSelector itemSelector
    )
    {
        _targetId = targetId;
        _skillsApi = skillsApi;
        _walkManager = walkManager;
        _skillSelector = skillSelector;
        _itemSelector = itemSelector;
    }

    /// <inheritdoc />
    public IReadOnlyList<OperationQueueType> HandlingQueueTypes => _handlingTypes;

    /// <inheritdoc />
    public bool ShouldContinue(ICombatState state)
    {
        var map = state.Game.CurrentMap;
        if (map is null)
        {
            return false;
        }

        if (_target is null)
        {
            _target = map.Entities.GetEntity<ILivingEntity>(_targetId);
        }

        return !(_target is null || (_target.Hp is not null && (_target.Hp.Amount <= 0 || _target.Hp.Percentage <= 0)));
    }

    /// <inheritdoc />
    public Result HandleWaiting(OperationQueueType queueType, ICombatState state, ICombatOperation operation, CannotBeUsedError cannotBeUsedError)
    {
        if (cannotBeUsedError.UnderlyingError is TargetDeadError)
        { // target is dead, that means exiting the technique. For that, remove the operation.
            state.RemoveCurrentOperation(queueType, true);
            return Result.FromSuccess();
        }

        if (cannotBeUsedError.Response == CanBeUsedResponse.WontBeUsable)
        { // Unfortunately we cannot handle that error.
            return cannotBeUsedError;
        }

        return Result.FromSuccess();
    }

    /// <inheritdoc />
    public Result HandleError(ICombatState state, Result result)
    {
        if (result.Error is NotInRangeError)
        { // the entity has moved between WalkInRange and UseSkill operations. That is unlikely
          // but it's better to be sure in case that happens it gets solved
            state.RemoveCurrentOperation(OperationQueueType.TotalControl, true);
            return Result.FromSuccess();
        }

        return result;
    }

    private Result<long?> HandleTotalControl(ICombatState state)
    {
        var map = state.Game.CurrentMap;
        if (map is null)
        {
            return new MapNotInitializedError();
        }

        if (_target is null)
        {
            var entity = map.Entities.GetEntity<ILivingEntity>(_targetId);
            if (entity is null)
            {
                return new EntityNotFoundError();
            }

            _target = entity;
        }

        var character = state.Game.Character;
        if (character is null)
        {
            return new CharacterNotInitializedError();
        }

        var skills = state.Game.Skills;
        if (skills is null)
        {
            return new CharacterNotInitializedError("Skills");
        }

        var characterMp = character.Mp?.Amount ?? 0;
        var usableSkills = skills.AllSkills
            .Where
            (
                x => x.Info is not null && x.Info.HitType != HitType.AlliesInZone
                    && x.Info.SkillType == SkillType.Player &&
                    !x.IsOnCooldown && characterMp >= (x.Info?.MpCost ?? long.MaxValue) &&
                    !x.Info!.IsComboSkill()
            );

        var skillResult = _skillSelector.GetSelectedSkill(usableSkills);
        if (!skillResult.IsDefined(out var currentSkill))
        {
            if (skillResult.Error is SkillNotFoundError)
            {
                return _target.Id;
            }

            return Result<long?>.FromError(skillResult);
        }

        if (currentSkill.Info is null)
        {
            return new MissingInfoError("skill", currentSkill.SkillVNum);
        }

        if (character.Position is null)
        {
            return new CharacterNotInitializedError("Position");
        }

        if (_target.Position is null)
        {
            return new EntityNotFoundError();
        }

        var range = currentSkill.Info.Range;
        if (currentSkill.Info.TargetType == TargetType.Self && currentSkill.Info.HitType == HitType.EnemiesInZone
            && currentSkill.Info.AttackType != AttackType.Dash)
        {
            range = currentSkill.Info.ZoneRange;
        }

        state.EnqueueOperation
        (
            new CompoundOperation
            (
                this,
                OperationQueueType.TotalControl,
                new WalkInRangeOperation(_walkManager, _target, range),
                new UseSkillOperation(_skillsApi, currentSkill, character, _target)
            )
        );
        return _target.Id;
    }

    private Result<long?> HandleItem(ICombatState state)
    {
        var shouldUseItemResult = _itemSelector.ShouldUseItem(state);
        if (!shouldUseItemResult.IsDefined(out var shouldUseItem))
        {
            return Result<long?>.FromError(shouldUseItemResult);
        }

        if (!shouldUseItem)
        {
            return _targetId;
        }

        var inventory = state.Game.Inventory;
        if (inventory is null)
        {
            return _targetId;
        }

        var main = inventory.GetBag(BagType.Main)
            .Where(x => x is { Amount: > 0, Item.Info.Type: ItemType.Potion })
            .Select(x => new InventoryItem(BagType.Main, x));
        var etc = inventory.GetBag(BagType.Etc)
            .Where(x => x is { Amount: > 0, Item.Info.Type: ItemType.Food or ItemType.Snack })
            .Select(x => new InventoryItem(BagType.Etc, x));

        var possibleItems = main.Concat(etc).ToList();

        var itemResult = _itemSelector.GetSelectedItem(state, possibleItems);

        if (!itemResult.IsDefined(out var item))
        {
            return Result<long?>.FromError(itemResult);
        }

        state.UseItem(item);
        return _targetId;
    }

    /// <inheritdoc />
    public Result<long?> HandleNextCombatStep(OperationQueueType queueType, ICombatState state)
    {
        switch (queueType)
        {
            case OperationQueueType.Item:
                return HandleItem(state);
            case OperationQueueType.TotalControl:
                return HandleTotalControl(state);
        }

        throw new InvalidOperationException("SimpleAttackTechnique supports only Item and TotalControl queue types.");
    }
}