using PkmnLib.Dynamic.Libraries;

namespace PkmnLib.Plugin.Gen7.Libraries.Battling;

public class Gen7BattleStatCalculator : IBattleStatCalculator
{
    /// <inheritdoc />
    public void CalculateFlatStats(IPokemon pokemon, StatisticSet<uint> stats)
    {
        stats.SetStatistic(Statistic.Hp, CalculateHealthStat(pokemon));
        stats.SetStatistic(Statistic.Attack, CalculateNormalStat(pokemon, Statistic.Attack));
        stats.SetStatistic(Statistic.Defense, CalculateNormalStat(pokemon, Statistic.Defense));
        stats.SetStatistic(Statistic.SpecialAttack, CalculateNormalStat(pokemon, Statistic.SpecialAttack));
        stats.SetStatistic(Statistic.SpecialDefense, CalculateNormalStat(pokemon, Statistic.SpecialDefense));
        stats.SetStatistic(Statistic.Speed, CalculateNormalStat(pokemon, Statistic.Speed));
    }

    /// <inheritdoc />
    public uint CalculateFlatStat(IPokemon pokemon, Statistic stat)
    {
        return stat switch
        {
            Statistic.Hp => CalculateHealthStat(pokemon),
            _ => CalculateNormalStat(pokemon, stat),
        };
    }

    /// <inheritdoc />
    public void CalculateBoostedStats(IPokemon pokemon, StatisticSet<uint> stats)
    {
        stats.SetStatistic(Statistic.Hp, CalculateBoostedStat(pokemon, Statistic.Hp));
        stats.SetStatistic(Statistic.Attack, CalculateBoostedStat(pokemon, Statistic.Attack));
        stats.SetStatistic(Statistic.Defense, CalculateBoostedStat(pokemon, Statistic.Defense));
        stats.SetStatistic(Statistic.SpecialAttack, CalculateBoostedStat(pokemon, Statistic.SpecialAttack));
        stats.SetStatistic(Statistic.SpecialDefense, CalculateBoostedStat(pokemon, Statistic.SpecialDefense));
        stats.SetStatistic(Statistic.Speed, CalculateBoostedStat(pokemon, Statistic.Speed));
    }

    /// <inheritdoc />
    public uint CalculateBoostedStat(IPokemon pokemon, Statistic stat)
    {
        var flatStat = CalculateFlatStat(pokemon, stat);
        var boostModifier = GetStatBoostModifier(pokemon.StatBoost.GetStatistic(stat));
        var boostedStat = flatStat * boostModifier;
        if (boostedStat > uint.MaxValue)
            boostedStat = uint.MaxValue;
        return (uint)boostedStat;
    }

    /// <inheritdoc />
    public byte CalculateModifiedAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex,
        byte moveAccuracy)
    {
        var accuracyModifier = 1.0f;
        executingMove.RunScriptHook<IScriptChangeAccuracyModifier>(x =>
            x.ChangeAccuracyModifier(executingMove, target, hitIndex, ref accuracyModifier));
        var modifiedAccuracy = (int)(moveAccuracy * accuracyModifier);
        // ReSharper disable once AccessToModifiedClosure
        executingMove.RunScriptHook<IScriptChangeAccuracy>(x =>
            x.ChangeAccuracy(executingMove, target, hitIndex, ref modifiedAccuracy));
        if (modifiedAccuracy >= 255)
            return 255;
        target.RunScriptHook<IScriptChangeIncomingAccuracy>(x =>
            x.ChangeIncomingAccuracy(executingMove, target, hitIndex, ref modifiedAccuracy));
        if (modifiedAccuracy >= 255)
            return 255;
        var targetEvasion = target.StatBoost.Evasion;
        var ignoreEvasion = false;
        executingMove.RunScriptHook<IScriptBypassEvasionStatBoosts>(x =>
            x.BypassEvasionStatBoosts(executingMove, target, hitIndex, ref ignoreEvasion));
        if (ignoreEvasion)
            targetEvasion = 0;
        var userAccuracy = executingMove.User.StatBoost.Accuracy;
        var statModifier = GetAccuracyEvasionStatModifier(targetEvasion, userAccuracy);
        modifiedAccuracy = (int)(modifiedAccuracy * statModifier);
        modifiedAccuracy = modifiedAccuracy switch
        {
            > 255 => 255,
            < 0 => 0,
            _ => modifiedAccuracy,
        };
        // NOTE: the main games also consider friendship here, but we don't yet have the concept of a "player Pokémon"
        //       in the battle system, so for now we're just ignoring that.
        return (byte)modifiedAccuracy;
    }

    private static uint CalculateHealthStat(IPokemon pokemon)
    {
        var baseValue = (ulong)pokemon.Form.BaseStats.Hp;
        var iv = (ulong)pokemon.IndividualValues.Hp;
        var ev = (ulong)pokemon.EffortValues.Hp;
        var level = (ulong)pokemon.Level;
        var health = (2 * baseValue + iv + ev / 4) * level / 100 + level + 10;
        if (health > uint.MaxValue)
            health = uint.MaxValue;
        return (uint)health;
    }

    private static uint CalculateNormalStat(IPokemon pokemon, Statistic statistic)
    {
        var baseValue = (ulong)pokemon.Form.BaseStats.GetStatistic(statistic);
        var iv = (ulong)pokemon.IndividualValues.GetStatistic(statistic);
        var ev = (ulong)pokemon.EffortValues.GetStatistic(statistic);
        var level = (ulong)pokemon.Level;
        var unmodified = (2 * baseValue + iv + ev / 4) * level / 100 + 5;
        var natureModifier = pokemon.Nature.GetStatModifier(statistic);
        var modified = unmodified * natureModifier;
        if (modified > uint.MaxValue)
            modified = uint.MaxValue;
        return (uint)modified;
    }

    public static float GetStatBoostModifier(sbyte amount)
    {
        return amount switch
        {
            -6 => 2.0f / 8.0f,
            -5 => 2.0f / 7.0f,
            -4 => 2.0f / 6.0f,
            -3 => 2.0f / 5.0f,
            -2 => 2.0f / 4.0f,
            -1 => 2.0f / 3.0f,
            0 => 1.0f,
            1 => 3.0f / 2.0f,
            2 => 4.0f / 2.0f,
            3 => 5.0f / 2.0f,
            4 => 6.0f / 2.0f,
            5 => 7.0f / 2.0f,
            6 => 8.0f / 2.0f,
            _ => throw new ArgumentException($"Stat boost was out of expected range of -6 to 6: {amount}"),
        };
    }

    public static float GetAccuracyEvasionStatModifier(sbyte evasion, sbyte accuracy)
    {
        var difference = evasion - accuracy;
        return difference switch
        {
            > 0 => 3.0f / (3.0f + Math.Min(difference, 6)),
            < 0 => 3.0f + -Math.Max(difference, -6) / 3.0f,
            _ => 1.0f,
        };
    }
}