using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Static.Utils;

namespace PkmnLib.Dynamic.AI;

/// <summary>
/// PrescientAI is an AI that predicts the best move based on the current state of the battle.
/// This is slightly cheaty, as it simulates the battle with each possible move to find the best one.
/// </summary>
public class PrescientAI : PokemonAI
{
    private static readonly PokemonAI OpponentAI = new HighestDamageAI();

    /// <inheritdoc />
    public PrescientAI() : base("Prescient")
    {
    }

    /// <inheritdoc />
    public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon)
    {
        var opponentSide = pokemon.BattleData!.SideIndex == 0 ? (byte)1 : (byte)0;
        var moves = pokemon.Moves.WhereNotNull().Where(x => battle.CanUse(new MoveChoice(pokemon, x, opponentSide, 0)))
            .ToList();

        var choices = ScoreChoices(battle, moves, pokemon).OrderByDescending(x => x.Score).ToList();
        if (choices.Count == 0)
        {
            return battle.Library.MiscLibrary.ReplacementChoice(pokemon, opponentSide, 0);
        }
        var bestChoice = choices.First().Choice;
        return bestChoice;
    }

    private static IEnumerable<(ITurnChoice Choice, float Score)> ScoreChoices(IBattle battle,
        IReadOnlyList<ILearnedMove> moves, IPokemon pokemon)
    {
        var opponentSide = pokemon.BattleData!.SideIndex == 0 ? (byte)1 : (byte)0;
        foreach (var learnedMoveOriginal in moves.WhereNotNull())
        {
            var battleClone = battle.DeepClone();
            var pokemonClone = battleClone.Sides[pokemon.BattleData!.SideIndex].Pokemon[pokemon.BattleData.Position]!;
            var learnedMove = pokemonClone.Moves.WhereNotNull()
                .First(m => m.MoveData.Name == learnedMoveOriginal.MoveData.Name);
            var choice = new MoveChoice(pokemonClone, learnedMove, opponentSide, 0);
            var opponentChoice = GetOpponentChoice(battleClone, pokemonClone);
            if (!battleClone.TrySetChoice(opponentChoice))
            {
                var replacementChoice =
                    battleClone.Library.MiscLibrary.ReplacementChoice(pokemonClone, opponentSide, 0);
                if (!battleClone.TrySetChoice(replacementChoice))
                {
                    throw new InvalidOperationException(
                        "Could not set opponent choice or replacement choice in battle clone.");
                }
            }
            if (battleClone.TrySetChoice(choice))
            {
                var score = CalculateScore(battleClone.Parties[pokemon.BattleData.SideIndex],
                    battleClone.Parties[opponentSide]);
                var realChoice = new MoveChoice(pokemon, learnedMoveOriginal, opponentSide, 0);
                yield return (realChoice, score);
            }
        }
    }

    private static ITurnChoice GetOpponentChoice(IBattle battle, IPokemon pokemon)
    {
        var opponentSide = pokemon.BattleData!.SideIndex == 0 ? (byte)1 : (byte)0;
        var opponent = battle.Sides[opponentSide].Pokemon[0];
        if (opponent is null)
        {
            throw new InvalidOperationException("Opponent Pokemon is null.");
        }
        if (battle.HasForcedTurn(opponent, out var forcedChoice))
        {
            return forcedChoice;
        }

        return OpponentAI.GetChoice(battle, opponent);
    }

    private static float CalculateScore(IBattleParty ownParty, IBattleParty opponentParty) =>
        ownParty.Party.WhereNotNull().Sum(x => x.CurrentHealth / (float)x.MaxHealth) -
        opponentParty.Party.WhereNotNull().Sum(x => x.CurrentHealth / (float)x.MaxHealth);
}