using System.Diagnostics.CodeAnalysis;
using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static.Moves;
using PkmnLib.Static.Utils;

namespace PkmnLib.Dynamic.AI.Explicit;

public partial class ExplicitAI
{
    private bool TryChooseToSwitchOut(IBattle battle, IPokemon pokemon, bool terribleMoves,
        [NotNullWhen(true)] out ITurnChoice? choice)
    {
        choice = null;
        if (battle.IsWildBattle)
            return false;
        if (TrainerHighSkill)
        {
            var opponentSide = battle.Sides.First(x => x != pokemon.BattleData?.BattleSide);
            var foeCanAct = opponentSide.Pokemon.WhereNotNull().Any(CanAttack);
            if (!foeCanAct)
                return false;
        }
        var party = battle.Parties.FirstOrDefault(x => x.IsResponsibleForIndex(
            new ResponsibleIndex(pokemon.BattleData!.SideIndex, pokemon.BattleData.Position)));
        if (party is null)
            return false;
        var usablePokemon = party.GetUsablePokemonNotInField().ToList();
        if (!terribleMoves)
        {
            if (!_skillFlags.ConsiderSwitching)
                return false;
            if (!usablePokemon.Any())
                return false;
            var shouldSwitch = _handlers.ShouldSwitch(this, pokemon, battle, usablePokemon);
            if (shouldSwitch && TrainerMediumSkill)
            {
                if (_handlers.ShouldNotSwitch(this, pokemon, battle, usablePokemon))
                {
                    shouldSwitch = false;
                }
            }
            if (!shouldSwitch)
                return false;
        }
        var battleSide = pokemon.BattleData!.BattleSide;
        var bestReplacement = ChooseBestReplacementPokemon(terribleMoves, usablePokemon, battleSide);
        if (bestReplacement is null)
        {
            AILogging.LogInformation(
                $"ExplicitAI: No suitable replacement Pokemon found for {pokemon} at position {pokemon.BattleData.Position}.");
            return false;
        }
        choice = new SwitchChoice(pokemon, bestReplacement);
        return true;
    }

    private IPokemon? ChooseBestReplacementPokemon(bool terribleMoves, IReadOnlyList<IPokemon> usablePokemon,
        IBattleSide battleSide)
    {
        var options = usablePokemon.Where((_, index) =>
        {
            if (_skillFlags.ReserveLastPokemon && index == usablePokemon.Count - 1 && usablePokemon.Count > 1)
                return false; // Don't switch to the last Pokemon if there are others available.
            if (_skillFlags.UsePokemonInOrder && index != 0)
                return false;
            return true;
        }).ToList();
        if (options.Count == 0)
            return null;
        var ratedOptions = options
            .Select(pokemon => new { Pokemon = pokemon, Score = RateReplacementPokemon(pokemon, battleSide) })
            .OrderBy(x => x.Score).ToList();
        if (TrainerHighSkill && !terribleMoves)
        {
            if (ratedOptions.First().Score < 100)
                return null;
        }
        return ratedOptions.First().Pokemon;
    }

    private static readonly StringKey HeavyDutyBootsName = "heavy_duty_boots";
    private static readonly StringKey ToxicSpikesName = "toxic_spikes";
    private static readonly StringKey StickyWebName = "sticky_web";

    private int RateReplacementPokemon(IPokemon pokemon, IBattleSide battleSide)
    {
        var score = 0;
        var types = pokemon.Types;
        var entryDamage = CalculateEntryHazardDamage(pokemon, battleSide);
        if (entryDamage >= pokemon.CurrentHealth)
            score -= 50;
        else if (entryDamage > 0)
            score -= 50 * (int)Math.Round((double)entryDamage / pokemon.MaxHealth, MidpointRounding.AwayFromZero);
        if (!pokemon.HasHeldItem(HeavyDutyBootsName) && !pokemon.IsFloating)
        {
            if (battleSide.VolatileScripts.Contains(ToxicSpikesName) && CanBePoisoned(pokemon, battleSide.Battle))
            {
                score -= 20;
            }
            if (battleSide.VolatileScripts.Contains(StickyWebName))
            {
                score -= 15;
            }
        }
        var opponentSide = battleSide.Battle.Sides.First(x => x != battleSide);
        foreach (var foe in opponentSide.Pokemon.WhereNotNull())
        {
            var lastMoveUsed = foe.BattleData?.LastMoveChoice;
            if (lastMoveUsed is null || lastMoveUsed.ChosenMove.MoveData.Category == MoveCategory.Status)
                continue;
            var moveType = lastMoveUsed.ChosenMove.MoveData.MoveType;
            var effectiveness = pokemon.Library.StaticLibrary.Types.GetEffectiveness(moveType, types);
            score -= (int)(lastMoveUsed.ChosenMove.MoveData.BasePower * effectiveness / 5);
        }
        foreach (var learnedMove in pokemon.Moves.WhereNotNull())
        {
            if (learnedMove.MoveData.BasePower == 0 || learnedMove is { CurrentPp: 0, MaxPp: > 0 })
                continue;
            foreach (var foe in opponentSide.Pokemon.WhereNotNull())
            {
                if (CanAbsorbMove(foe, learnedMove.MoveData, learnedMove.MoveData.MoveType, battleSide.Battle))
                    continue;
                var effectiveness =
                    pokemon.Library.StaticLibrary.Types.GetEffectiveness(learnedMove.MoveData.MoveType, foe.Types);
                score += (int)(learnedMove.MoveData.BasePower * effectiveness / 10);
            }
        }

        return score;
    }

    /// <summary>
    /// Calculates the expected entry hazard damage for a given Pokémon on a given battle side.
    /// </summary>
    public static uint CalculateEntryHazardDamage(IPokemon pokemon, IBattleSide side)
    {
        var damage = 0u;
        side.RunScriptHook<IAIInfoScriptExpectedEntryDamage>(x => x.ExpectedEntryDamage(pokemon, ref damage));
        return damage;
    }

    private static bool CanSwitch(IPokemon pokemon)
    {
        var battleData = pokemon.BattleData;
        if (battleData == null)
            return false;
        if (battleData.Battle.IsWildBattle)
            return false;
        var partyForIndex = battleData.Battle.Parties.FirstOrDefault(x =>
            x.IsResponsibleForIndex(new ResponsibleIndex(battleData.SideIndex, battleData.Position)));
        return partyForIndex != null && partyForIndex.HasUsablePokemonNotInField();
    }
}