using System;
using System.Collections.Generic;
using PkmnLibSharp.Library;
using PkmnLibSharp.Library.Items;
using PkmnLibSharp.Library.Moves;
using Random = PkmnLibSharp.Utilities.Random;

namespace PkmnLibSharp.Battling
{
    public abstract class BasePokemonBuilder<TPokemon, TLearnedMove> 
        where TPokemon : Pokemon 
        where TLearnedMove :LearnedMove
    {
        protected readonly BattleLibrary Library;
        public string Species { get; }
        public string Forme { get; private set; } = "default";
        public string Nickname { get; private set; } = "";
        public string Ability { get; private set; } = "";
        public string Nature { get; private set; } = "";
        public Gender Gender { get; private set; } = (Gender) (-1);
        public uint Identifier { get; private set; } = 0;
        public byte Level { get; }

        public StatisticSet<byte> IVs;
        public StatisticSet<byte> EVs;
        
        public bool? IsForceShiny { get; private set; }
        public string? HeldItem { get; private set; }

        public List<(string moveName, MoveLearnMethod learnMethod)> LearnedMoves { get; } =
            new List<(string moveName, MoveLearnMethod learnMethod)>();
        
        public BasePokemonBuilder(BattleLibrary library, string species, byte level)
        {
            Library = library;
            Species = species;
            Level = level;
        }

        public BasePokemonBuilder<TPokemon, TLearnedMove> WithForme(string forme)
        {
            Forme = forme;
            return this;
        }

        public BasePokemonBuilder<TPokemon, TLearnedMove> WithGender(Gender gender)
        {
            Gender = gender;
            return this;
        }

        public BasePokemonBuilder<TPokemon, TLearnedMove> ForceShiny(bool value)
        {
            IsForceShiny = value;
            return this;
        }

        public BasePokemonBuilder<TPokemon, TLearnedMove> WithHeldItem(string heldItem)
        {
            HeldItem = heldItem;
            return this;
        }

        public BasePokemonBuilder<TPokemon, TLearnedMove> WithNickname(string nickname)
        {
            Nickname = nickname;
            return this;
        }
        
        public BasePokemonBuilder<TPokemon, TLearnedMove> WithNature(string nature)
        {
            Nature = nature;
            return this;
        }
        
        public BasePokemonBuilder<TPokemon, TLearnedMove> WithIVs(StatisticSet<byte> ivs)
        {
            IVs = ivs;
            return this;
        }
        public BasePokemonBuilder<TPokemon, TLearnedMove> WithEVs(StatisticSet<byte> evs)
        {
            EVs = evs;
            return this;
        }


        public BasePokemonBuilder<TPokemon, TLearnedMove> LearnMove(string moveName, MoveLearnMethod moveLearnMethod)
        {
            if (LearnedMoves.Count > Library.StaticLibrary.Settings.MaximalMoves)
            {
                throw new Exception("Too many moves for Pokemon.");
            }
            LearnedMoves.Add((moveName, moveLearnMethod));
            return this;
        }

        protected byte Coloring;
        protected uint Experience;
        protected uint Uid;
        protected int AbilityIndex;
        protected bool HiddenAbility;

        protected virtual void PopulateUninitialized(Species species, Forme forme, Random random)
        {
            Experience = Library.StaticLibrary.GrowthRateLibrary.CalculateExperience(species.GrowthRate, Level);
            Uid = random.GetUnsigned(0, uint.MaxValue);

            if (Gender == (Gender) (-1))
            {
                Gender = species.GetRandomGender(random);
            }
            Coloring = 0;
            if (IsForceShiny.HasValue)
            {
                Coloring = (byte) (IsForceShiny.Value ? 1 : 0);
            }
            else if (random.Get(Library.StaticLibrary.Settings.ShinyRate) == 0)
            {
                Coloring = 1;
            }

            AbilityIndex = 0;
            HiddenAbility = false;
            if (string.IsNullOrEmpty(Ability))
            {
                AbilityIndex = forme!.GetRandomAbility(random);
            }
            else
            {
                AbilityIndex = forme!.Abilities.IndexOf(Ability);
                if (AbilityIndex == -1)
                {
                    AbilityIndex = forme.HiddenAbilities.IndexOf(Ability);
                    if (AbilityIndex == -1)
                    {
                        throw new Exception(
                            $"Invalid ability '{Ability}' for Pokemon '{species.Name}' and forme '{forme.Name}'.");
                    }

                    HiddenAbility = true;
                }
            }
        }

        protected abstract TPokemon Finalize(Species species, Forme forme, Item? heldItem,
            IReadOnlyCollection<LearnedMove> moves, Nature nature);

        protected abstract TLearnedMove CreateLearnedMove(MoveData move, byte maxUses, MoveLearnMethod learnMethod);

        public TPokemon Build()
        {
            if (!Library.StaticLibrary.SpeciesLibrary.TryGet(Species, out var species))
            {
                throw new Exception($"Species '{Species}' was not found.");
            }

            if (!species!.TryGetForme(Forme, out var forme))
            {
                throw new Exception($"Forme '{Forme}' was not found on species '{Species}'");
            }

            var random = new Random();

            Item? heldItem = null;
            if (HeldItem != null)
            {
                if (!Library.StaticLibrary.ItemLibrary.TryGet(HeldItem, out heldItem))
                {
                    throw new Exception($"Item '{HeldItem}' was not found.");
                }
            }
            
            var moves = new LearnedMove[Library.StaticLibrary.Settings.MaximalMoves];
            for (var i = 0; i < LearnedMoves.Count; i++)
            {
                if (!Library.StaticLibrary.MoveLibrary.TryGet(LearnedMoves[i].moveName, out var move))
                {
                    throw new Exception($"Move '{LearnedMoves[i].moveName}' was not found.");
                }

                moves[i] = CreateLearnedMove(move!, move!.BaseUsages, LearnedMoves[i].learnMethod);
            }

            if (string.IsNullOrEmpty(Nature))
            {
                Nature = Library.StaticLibrary.NatureLibrary.GetRandomNatureName(random);
            }

            var nature = Library.StaticLibrary.NatureLibrary.GetNature(Nature);

            if (Gender == (Gender) (-1))
            {
                Gender = species.GetRandomGender(random);
            }
            
            PopulateUninitialized(species, forme!, random);
            return Finalize(species, forme!, heldItem, moves, nature);
        }
    }
}