using System;

namespace PKHeX.Core;

/// <summary>
/// Encounter Slot found in <see cref="EntityContext.Gen7b"/> (GO Park).
/// <inheritdoc cref="PogoSlotExtensions" />
/// </summary>
public sealed record EncounterSlot7GO(int StartDate, int EndDate, ushort Species, byte Form, byte LevelMin, byte LevelMax, Shiny Shiny, Gender Gender, PogoType Type)
    : IEncounterable, IEncounterMatch, IPogoSlot, IEncounterConvertible<PB7>, IEncounterServerDate
{
    public bool IsDateRestricted => true;
    public byte Generation => 7;
    public EntityContext Context => EntityContext.Gen7b;
    public Ball FixedBall => Ball.None; // GO Park can override the ball; obey capture rules for LGP/E
    public bool IsEgg => false;
    public AbilityPermission Ability => AbilityPermission.Any12;
    public bool IsShiny => Shiny.IsShiny();
    public ushort EggLocation => 0;

    public GameVersion Version => GameVersion.GO;
    public ushort Location => Locations.GO7;
    public string Name => $"GO Encounter ({Version})";
    public string LongName
    {
        get
        {
            var init = $"{Name} ({Type})";
            if (StartDate == 0 && EndDate == 0)
                return init;
            var start = PogoDateRangeExtensions.GetDateString(StartDate);
            var end = PogoDateRangeExtensions.GetDateString(EndDate);
            return $"{init}: {start}-{end}";
        }
    }

    #region Generating
    PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
    PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
    public PB7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);

    public PB7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
    {
        int language = (int)Language.GetSafeLanguage789((LanguageID)tr.Language);
        var rnd = Util.Rand;
        var date = this.GetRandomValidDate();
        var pk = new PB7
        {
            PID = EncounterUtil.GetRandomPID(tr, rnd, Shiny, criteria.Shiny),
            EncryptionConstant = rnd.Rand32(),
            Species = Species,
            Form = Form,
            CurrentLevel = LevelMin,
            OriginalTrainerFriendship = PersonalTable.GG[Species].BaseFriendship,
            MetLocation = Location,
            MetLevel = LevelMin,
            Version = Version,
            Ball = (byte)Ball.Poke,
            MetDate = date,

            Language = language,
            OriginalTrainerName = tr.OT,
            OriginalTrainerGender = tr.Gender,
            ID32 = tr.ID32,

            ReceivedDate = date,
            ReceivedTime = EncounterDate.GetTime(),
        };
        SetPINGA(pk, criteria);
        EncounterUtil.SetEncounterMoves(pk, Version, LevelMin);
        pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation);
        SetEncounterMoves(pk, LevelMin);
        pk.AwakeningSetAllTo(2);
        pk.HeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
        pk.WeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
        pk.ResetHeight();
        pk.ResetWeight();
        pk.ResetCP();
        pk.ResetPartyStats();
        return pk;
    }

    private void SetPINGA(PB7 pk, in EncounterCriteria criteria)
    {
        var pi = PersonalTable.GG[Species];
        var gender = criteria.GetGender(Gender, pi);
        var nature = criteria.GetNature();
        var ability = criteria.GetAbilityFromNumber(Ability);

        criteria.SetRandomIVsGO(pk, Type.GetMinIV());
        pk.Nature = pk.StatNature = nature;
        pk.Gender = gender;
        pk.RefreshAbility(ability);

        switch (Shiny)
        {
            case Shiny.Random when !pk.IsShiny && criteria.Shiny.IsShiny():
            case Shiny.Always when !pk.IsShiny: // Force Square
                var low = pk.PID & 0xFFFF;
                pk.PID = ((low ^ pk.TID16 ^ pk.SID16 ^ 0) << 16) | low;
                break;

            case Shiny.Random when pk.IsShiny && !criteria.Shiny.IsShiny():
            case Shiny.Never when pk.IsShiny: // Force Not Shiny
                pk.PID ^= 0x1000_0000;
                break;
        }
    }

    private void SetEncounterMoves(PB7 pk, byte level)
    {
        Span<ushort> moves = stackalloc ushort[4];
        var source = LearnSource7GG.Instance;
        ((ILearnSource)source).SetEncounterMoves(Species, Form, level, moves);
        pk.SetMoves(moves);
    }
    #endregion

    #region Matching

    public bool IsMatchExact(PKM pk, EvoCriteria evo)
    {
        // Find the first chain that has slots defined.
        // Since it is possible to evolve before transferring, we only need the highest evolution species possible.
        // PoGoEncTool has already extrapolated the evolutions to separate encounters!

        if (!this.IsLevelWithinRange(pk.MetLevel))
            return false;
        //if (!slot.IsBallValid(ball)) -- can have any of the in-game balls due to re-capture
        //    continue;
        if (!Shiny.IsValid(pk))
            return false;

        // At least one encounter is a single gender (Pikachu 24-11-04 yay...) so check.
        if (Gender != Gender.Random && (int)Gender != pk.Gender)
            return false;

        return true;
    }

    public bool IsWithinDistributionWindow(PKM pk)
    {
        var date = new DateOnly(pk.MetYear + 2000, pk.MetMonth, pk.MetDay);
        return IsWithinDistributionWindow(date);
    }

    public bool IsWithinDistributionWindow(DateOnly date)
    {
        var stamp = PogoDateRangeExtensions.GetTimeStamp(date.Year, date.Month, date.Day);
        return this.IsWithinStartEnd(stamp);
    }

    public EncounterMatchRating GetMatchRating(PKM pk)
    {
        var stamp = PogoDateRangeExtensions.GetTimeStamp(pk.MetYear + 2000, pk.MetMonth, pk.MetDay);
        if (!this.IsWithinStartEnd(stamp))
            return EncounterMatchRating.DeferredErrors;
        if (!this.GetIVsValid(pk))
            return EncounterMatchRating.Deferred;
        return EncounterMatchRating.Match;
    }
    #endregion
}
