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

namespace Nox.Game {
    public interface IModifier {
        string Name { get; }
        Guid Id { get; }
        ModifierTarget Target { get; }
        ModifierTarget ScalingSource { get; }
        ModifierOperation Operation { get; }
        float Value { get; }
        IReadOnlyList<ModifierRequirement> Requirements { get; }
    }

    public interface IModifiersFactory {
        IReadOnlyCollection<IModifier> GetAll();
        IModifier GetById(Guid modifierId);
        IReadOnlyCollection<IModifier> GetModifiersFor(IEntityDefinition character);
        bool TryAddModifier(IEntityDefinition character, Guid modiferId);
    }

    public enum ModifierOperation {
        None,
        Flat,
        Addition,
        Multiplication,
        Percentage
    }

    public enum ModifierTargetType {
        None,
        Attribute,
        Stat,
        CombatScore
    }

    [Serializable]
    public sealed class ModifierTarget {
        [field: SerializeField] public ModifierTargetType Type { get; set; }
        [field: SerializeField] public AttributeType AttributeType { get; set; }
        [field: SerializeField] public StatType StatType { get; set; }
        [field: SerializeField] public CombatScoreType CombatScoreType { get; set; }

        public bool Matches(StatType statType) {
            return Type == ModifierTargetType.Stat && StatType == statType;
        }

        public bool Matches(AttributeType attributeType) {
            return Type == ModifierTargetType.Attribute && AttributeType == attributeType;
        }

        public bool Matches(CombatScoreType combatScoreType) {
            return Type == ModifierTargetType.CombatScore && CombatScoreType == combatScoreType;
        }

        public bool IsSet => Type != ModifierTargetType.None;

        public override string ToString() {
            return Type switch {
                ModifierTargetType.Attribute => AttributeType.ToString(),
                ModifierTargetType.Stat => StatType.ToString(),
                ModifierTargetType.CombatScore => CombatScoreType.ToString(),
                _ => "None"
            };
        }
    }

    [Serializable]
    public sealed class ModifierRequirement {
        [field: SerializeField] public AttributeType Attribute { get; set; }
        [field: SerializeField] public int MinimumValue { get; set; }

        public bool IsMet(EntityAttributes attributes) {
            if(Attribute == AttributeType.None || attributes?.attributes == null) {
                return true;
            }
            return attributes.GetValue(Attribute) >= MinimumValue;
        }
    }

    [Serializable]
    public sealed class ModifierDefinition : IModifier {
        [field: SerializeField] public string Name { get; set; }
        public Guid Id { get; set; } = Guid.NewGuid();
        [field: SerializeField] public ModifierTarget Target { get; set; }
        [field: SerializeField] public ModifierTarget ScalingSource { get; set; }
        [field: SerializeField] public ModifierOperation Operation { get; set; }
        [field: SerializeField] public float Value { get; set; }
        [field: SerializeField] public List<ModifierRequirement> Requirements { get; set; } = new();

        IReadOnlyList<ModifierRequirement> IModifier.Requirements => Requirements;
    }

    [Serializable]
    public sealed class ModifiersData {
        public List<ModifierDefinition> modifiers = new();

        public override string ToString() {
            return $"Modifiers: {string.Join(", ", modifiers.Select(modifier => $"{modifier.Name}"))}";
        }
    }

    public class ModifiersFactory : IModifiersFactory {
        private readonly ModifiersRegistry modifiersRegistry;
        private readonly Dictionary<Guid, IModifier> modifierPool = new();

        public ModifiersFactory(ModifiersRegistry modifiersRegistry) {
            this.modifiersRegistry = modifiersRegistry;
            var allAvailableModifiers = modifiersRegistry.modifiersData;
            foreach(var modifier in allAvailableModifiers.modifiers) {
                modifierPool.Add(modifier.Id, modifier);
            }
        }

        public IReadOnlyCollection<IModifier> GetAll() {
            return modifiersRegistry.modifiersData.modifiers;
        }
        public IModifier GetById(Guid modifierId) {
            return modifiersRegistry.modifiersData.modifiers.AsValueEnumerable().FirstOrDefault(m => m.Id == modifierId);
        }
        public IReadOnlyCollection<IModifier> GetModifiersFor(IEntityDefinition character) {
            return character.Modifiers.modifiers;
        }
        public bool TryAddModifier(IEntityDefinition character, Guid modifierId) {
            if(character == null) {
                return false;
            }

            if(modifierId == Guid.Empty) {
                GlobalLogger.LogException("Cannot add a modifier with an empty ID!", LogCategory.GameLogic);
                return false;
            }

            if(character.Modifiers.modifiers.AsValueEnumerable().Any(p => p != null && p.Id == modifierId)) {
                return false;
            }

            if(!modifierPool.TryGetValue(modifierId, out var modifier)) {
                return false;
            }

            character.Modifiers.modifiers.Add(new ModifierDefinition {
                Id = modifier.Id,
                Name = modifier.Name,
                Target = modifier.Target,
                ScalingSource = modifier.ScalingSource,
                Operation = modifier.Operation,
                Value = modifier.Value,
                Requirements = modifier.Requirements?.ToList() ?? new List<ModifierRequirement>()
            });

            return true;
        }
    }
}
