using Jovian.Logger;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Nox.Game {

    public interface IPerk {
        Guid Id { get; }
        string Name { get; }
        ModifiersData Modifiers { get; }
    }

    public interface IPerkFactory {
        IReadOnlyCollection<IPerk> GetAll();
        IPerk GetById(Guid perkId);
        IReadOnlyCollection<IPerk> GetPerksFor(IEntityDefinition character);
        bool TryAddPerk(IEntityDefinition character, Guid perkId);
    }

    [Serializable]
    public sealed class PerkDefinition : IPerk {
        [field: SerializeField] public string Name { get; set; }
        [field: SerializeField] public ModifiersData Modifiers { get; set; }
        public Guid Id { get; set; }
    }

    [Serializable]
    public sealed class PerksData {
        public List<PerkDefinition> perks = new ();
    }

    public sealed class PerkFactory : IPerkFactory {
        private readonly Dictionary<Guid, IPerk> perkPool = new ();

        public PerkFactory(PerksRegistry perksRegistry) {
            if(!perksRegistry) {
                throw new ArgumentNullException(nameof(perksRegistry));
            }
            var allAvailablePerks = perksRegistry.perksData;
            foreach(var perk in allAvailablePerks.perks) {
                perkPool.Add(perk.Id, perk);
            }
        }

        public IReadOnlyCollection<IPerk> GetAll() {
            return perkPool.Values.ToList();
        }

        public IPerk GetById(Guid perkId) {
            perkPool.TryGetValue(perkId, out var perk);
            return perk;
        }

        public IReadOnlyCollection<IPerk> GetPerksFor(IEntityDefinition character) {
            if(character == null) {
                return perkPool.Values.ToList();
            }

            var ownedPerkIds = character.Perks.perks
                .Select(p => p.Id)
                .ToHashSet();

            return perkPool.Values.Where(p => !ownedPerkIds.Contains(p.Id)).ToList();
        }

        public bool TryAddPerk(IEntityDefinition character, Guid perkId) {
            if(character == null) {
                return false;
            }

            if(perkId == Guid.Empty) {
                GlobalLogger.LogException("Cannot add a perk with an empty Id!", LogCategory.GameLogic);
            }

            if(character.Perks.perks.Any(p => p != null && p.Id == perkId)) {
                return false;
            }

            if(!perkPool.TryGetValue(perkId, out var perk)) {
                return false;
            }

            character.Perks.perks.Add(new PerkDefinition {
                Id = perk.Id,
                Name = perk.Name,
                Modifiers = perk.Modifiers
            });

            return true;
        }
    }
}
