using System;
using System.Linq;

namespace Nox.Game {
    public interface ICharacterFactory {
        CharacterDefinition CreateCustomProtagonist(CustomCharacterCreationRequest request);
        CharacterDefinition CreateFromTemplate(CharacterTemplate template, CharacterRole role = CharacterRole.Companion);
    }

    [Serializable]
    public sealed class CharacterTemplate : IEntityDefinition {
        public Guid Id { get; set; } = Guid.NewGuid();
        public string Name { get; set; } = "New Character";
        public CharacterRace Race { get; set; } = (CharacterRace)GetRandomInt(1, Enum.GetValues(typeof(CharacterRace)).Length-1);
        public CharacterClass Class { get; set; } = (CharacterClass)GetRandomInt(1, Enum.GetValues(typeof(CharacterClass)).Length-1);
        public CharacterRole Role { get; set; } = CharacterRole.Companion;
        public EntityAttributes Attributes { get; set; } = GetDefaultAttributes();
        public EntityStats Stats { get; set; } = new();
        public PerksData Perks { get; set; } = new();
        public ModifiersData Modifiers { get; set; } = new();

        private static int GetRandomInt(int start, int end) => new Random().Next(start, end);
        private static EntityAttributes GetDefaultAttributes() {
            var attributes = new EntityAttributes {
                attributes = new Attribute[] {
                    new (AttributeType.Might, 1),
                    new (AttributeType.Knowledge, 1),
                    new (AttributeType.Perception, 1),
                    new (AttributeType.Reflex, 1)
                }
            };

            return attributes;
        }
    }

    [Serializable]
    public sealed class CustomCharacterCreationRequest : IEntityDefinition {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public CharacterRace Race { get; set; }
        public CharacterClass Class { get; set; }
        public CharacterRole Role { get; set; }
        public EntityAttributes Attributes { get; set; }
        public EntityStats Stats { get; set; }
        public PerksData Perks { get; set; }
        public ModifiersData Modifiers { get; set; }
    }

    public sealed class CharacterFactory : ICharacterFactory {
        private readonly ICharacterAttributesFactory attributesFactory;
        private readonly ICharacterStatsFactory statsFactory;
        private readonly IPerkFactory perkFactory;
        private readonly IModifiersFactory modifiersFactory;

        public CharacterFactory(
            ICharacterAttributesFactory attributesFactory,
            ICharacterStatsFactory statsFactory,
            IPerkFactory perkFactory,
            IModifiersFactory modifiersFactory) {
            this.attributesFactory = attributesFactory ?? throw new ArgumentNullException(nameof(attributesFactory));
            this.statsFactory = statsFactory ?? throw new ArgumentNullException(nameof(statsFactory));
            this.perkFactory = perkFactory ?? throw new ArgumentNullException(nameof(perkFactory));
            this.modifiersFactory = modifiersFactory ?? throw new ArgumentNullException(nameof(modifiersFactory));
        }

        public CharacterDefinition CreateCustomProtagonist(CustomCharacterCreationRequest request) {
            if(request == null) {
                throw new ArgumentNullException(nameof(request));
            }

            var attributes = attributesFactory.Create(request.Attributes);
            var stats = statsFactory.Create(request);

            var character = new CharacterDefinition {
                Id = request.Id == Guid.Empty ? Guid.NewGuid() : request.Id,
                Name = request.Name,
                Race = request.Race,
                Class = request.Class,
                Role = CharacterRole.Protagonist,
                Attributes = attributes,
                Perks = request.Perks ?? new PerksData(),
                Modifiers = request.Modifiers ?? new ModifiersData()
            };

            AddStartingPerks(character, request.Perks);
            return character;
        }

        public CharacterDefinition CreateFromTemplate(CharacterTemplate template, CharacterRole role = CharacterRole.Companion) {
            if(template == null) {
                throw new ArgumentNullException(nameof(template));
            }

            template.Perks ??= new PerksData();
            template.Modifiers ??= new ModifiersData();

            var character = new CharacterDefinition {
                Id = template.Id == Guid.Empty ? Guid.NewGuid() : template.Id,
                Name = template.Name,
                Race = template.Race,
                Class = template.Class,
                Role = role,
                Attributes = attributesFactory.Create(template.Attributes),
                Stats = statsFactory.Create(template),
                Perks = template.Perks,
                Modifiers = template.Modifiers
            };

            AddStartingPerks(character, template.Perks);
            AddStartingModifiers(character, template.Modifiers);
            return character;
        }

        private void AddStartingPerks(CharacterDefinition character, PerksData perkData) {
            if(perkData?.perks == null || perkData.perks.Count == 0) {
                return;
            }

            foreach(var perkId in perkData.perks.Distinct()) {
                perkFactory.TryAddPerk(character, perkId.Id);
            }
        }

        private void AddStartingModifiers(CharacterDefinition character, ModifiersData modifiersData) {
            if(modifiersData?.modifiers == null || modifiersData.modifiers.Count == 0) {
                return;
            }

            foreach(var modifierId in modifiersData.modifiers.Distinct()) {
                modifiersFactory.TryAddModifier(character, modifierId.Id);
            }
        }
    }
}
