using System.Diagnostics.CodeAnalysis;
using PkmnLib.Static;
using PkmnLib.Static.Species;

namespace PkmnLib.Dynamic.Models.Serialized;

/// <summary>
/// A serialized Pokémon is a representation of a Pokémon that can be easily serialized and deserialized.
/// </summary>
public record SerializedPokemon
{
    /// <inheritdoc cref="SerializedPokemon"/>
    public SerializedPokemon()
    {
    }

    /// <inheritdoc cref="SerializedPokemon"/>
    [SetsRequiredMembers]
    public SerializedPokemon(IPokemon pokemon)
    {
        Species = pokemon.Species.Name;
        Form = pokemon.Form.Name;
        Level = pokemon.Level;
        Experience = pokemon.Experience;
        PersonalityValue = pokemon.PersonalityValue;
        Gender = pokemon.Gender;
        Coloring = pokemon.Coloring;
        HeldItem = pokemon.HeldItem?.Name;
        CurrentHealth = pokemon.CurrentHealth;
        Happiness = pokemon.Happiness;
        IndividualValues = new SerializedStats(pokemon.IndividualValues);
        EffortValues = new SerializedStats(pokemon.EffortValues);
        Nature = pokemon.Nature.Name;
        Nickname = pokemon.Nickname;
        Ability = pokemon.Form.GetAbility(pokemon.AbilityIndex);
        Moves = pokemon.Moves.Select(move =>
        {
            if (move == null)
                return null;
            return new SerializedLearnedMove
            {
                MoveName = move.MoveData.Name,
                LearnMethod = move.LearnMethod,
                CurrentPp = move.CurrentPp,
            };
        }).ToArray();
        AllowedExperience = pokemon.AllowedExperience;
        IsEgg = pokemon.IsEgg;
        Status = pokemon.StatusScript.Script?.Name;
    }

    /// <inheritdoc cref="IPokemon.Species"/>
    public required string Species { get; set; }

    /// <inheritdoc cref="IPokemon.Form"/>
    public required string Form { get; set; }

    /// <inheritdoc cref="IPokemon.Level"/>
    public LevelInt Level { get; set; }

    /// <inheritdoc cref="IPokemon.Experience"/>
    public uint Experience { get; set; }

    /// <inheritdoc cref="IPokemon.PersonalityValue"/>
    public uint PersonalityValue { get; set; }

    /// <inheritdoc cref="IPokemon.Gender"/>
    public Gender Gender { get; set; }

    /// <inheritdoc cref="IPokemon.Coloring"/>
    public byte Coloring { get; set; }

    /// <inheritdoc cref="IPokemon.HeldItem"/>
    public string? HeldItem { get; set; }

    /// <inheritdoc cref="IPokemon.CurrentHealth"/>
    public uint CurrentHealth { get; set; }

    /// <inheritdoc cref="IPokemon.Happiness"/>
    public byte Happiness { get; set; }

    /// <inheritdoc cref="IPokemon.IndividualValues"/>
    public required SerializedStats IndividualValues { get; set; }

    /// <inheritdoc cref="IPokemon.EffortValues"/>
    public required SerializedStats EffortValues { get; set; }

    /// <inheritdoc cref="IPokemon.Nature"/>
    public required string Nature { get; set; }

    /// <inheritdoc cref="IPokemon.Nickname"/>
    public string? Nickname { get; set; }

    /// <summary>
    /// The ability of the Pokémon. Note that this is the ability name, not the ability index, as the ability index might
    /// change if the order of abilities changes in data.
    /// </summary>
    public required string Ability { get; set; }

    /// <inheritdoc cref="IPokemon.Moves"/>
    public required SerializedLearnedMove?[] Moves { get; set; }

    /// <inheritdoc cref="IPokemon.AllowedExperience"/>
    public bool AllowedExperience { get; set; }

    /// <inheritdoc cref="IPokemon.IsEgg"/>
    public bool IsEgg { get; set; }

    /// <summary>
    /// The status of the Pokémon. This is the name of the status script, if any exists.
    /// </summary>
    public string? Status { get; set; }
}

/// <summary>
/// A serialized learned move is a representation of a learned move that can be easily serialized and deserialized.
/// </summary>
public record SerializedLearnedMove
{
    /// <summary>
    /// The name of the move.
    /// </summary>
    public required string MoveName { get; set; }

    /// <inheritdoc cref="ILearnedMove.LearnMethod"/>
    public required MoveLearnMethod LearnMethod { get; set; }

    /// <inheritdoc cref="ILearnedMove.CurrentPp"/>
    public required byte CurrentPp { get; set; }
}

public record SerializedStats
{
    public SerializedStats()
    {
    }
    
    public SerializedStats(ImmutableStatisticSet<byte> stats)
    {
        Hp = stats.Hp;
        Attack = stats.Attack;
        Defense = stats.Defense;
        SpecialAttack = stats.SpecialAttack;
        SpecialDefense = stats.SpecialDefense;
        Speed = stats.Speed;
    }
    
    public SerializedStats(long hp, long attack, long defense, long specialAttack, long specialDefense, long speed)
    {
        Hp = hp;
        Attack = attack;
        Defense = defense;
        SpecialAttack = specialAttack;
        SpecialDefense = specialDefense;
        Speed = speed;
    }

    public long Hp { get; set; }
    public long Attack { get; set; }
    public long Defense { get; set; }
    public long SpecialAttack { get; set; }
    public long SpecialDefense { get; set; }
    public long Speed { get; set; }

    public IndividualValueStatisticSet ToIndividualValueStatisticSet()
    {
        if (Hp < 0 || Attack < 0 || Defense < 0 || SpecialAttack < 0 || SpecialDefense < 0 || Speed < 0)
            throw new InvalidOperationException("Stats cannot be negative.");
        if (Hp > byte.MaxValue || Attack > byte.MaxValue || Defense > byte.MaxValue || SpecialAttack > byte.MaxValue ||
            SpecialDefense > byte.MaxValue || Speed > byte.MaxValue)
            throw new InvalidOperationException("Stats cannot be higher than 255.");

        return new IndividualValueStatisticSet((byte)Hp, (byte)Attack, (byte)Defense, (byte)SpecialAttack,
            (byte)SpecialDefense, (byte)Speed);
    }

    public EffortValueStatisticSet ToEffortValueStatisticSet()
    {
        if (Hp < 0 || Attack < 0 || Defense < 0 || SpecialAttack < 0 || SpecialDefense < 0 || Speed < 0)
            throw new InvalidOperationException("Stats cannot be negative.");
        if (Hp > byte.MaxValue || Attack > byte.MaxValue || Defense > byte.MaxValue || SpecialAttack > byte.MaxValue ||
            SpecialDefense > byte.MaxValue || Speed > byte.MaxValue)
            throw new InvalidOperationException("Stats cannot be higher than 255.");

        return new EffortValueStatisticSet((byte)Hp, (byte)Attack, (byte)Defense, (byte)SpecialAttack,
            (byte)SpecialDefense, (byte)Speed);
    }
}