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 {
        public string id;
        public string displayName;
        public EntityAttributes attributes;
        public CharacterRace characterRace;
        public CharacterClass characterClass;
        public EntityStats stats;
        public PerksData perksData = new ();
    }

    [Serializable]
    public sealed class CustomCharacterCreationRequest {
        public string id;
        public string displayName;
        public CharacterRace characterRace;
        public CharacterClass characterClass;
        public EntityStats stats;
        public EntityAttributes attributes;
        public PerksData perksData = new();
        public ModifiersData modifiersData = new();
    }

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

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

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

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

            var character = new CharacterDefinition {
                ID = string.IsNullOrWhiteSpace(request.id) ? Guid.NewGuid().ToString("N") : request.id,
                DisplayName = request.displayName,
                race = request.characterRace,
                @class = request.characterClass,
                role = CharacterRole.Protagonist,
                Attributes = attributes,
                Stats = stats,
                perksData = request.perksData ?? new PerksData(),
                activeModifiers = request.modifiersData ?? new ModifiersData()
            };

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

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

            var attributes = attributesFactory.Create(template.attributes);

            var character = new CharacterDefinition {
                ID = string.IsNullOrWhiteSpace(template.id) ? Guid.NewGuid().ToString("N") : template.id,
                DisplayName = template.displayName,
                race = template.characterRace,
                @class = template.characterClass,
                role = role,
                Attributes = attributes,
                Stats = statsFactory.Create(attributes),
                perksData = new PerksData(),
                activeModifiers = new ModifiersData()
            };

            AddStartingPerks(character, template.perksData);
            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);
            }
        }
    }
}
