using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;
using VContainer;
using YachtDice.Categories;
using YachtDice.Dice;
using YachtDice.Events;
using YachtDice.Modifiers.Core;
using YachtDice.Modifiers.Runtime;

namespace YachtDice.Scoring
{
    public class ScoringSystem : MonoBehaviour
    {
        public event Action<CategoryDefinition, int> OnCategoryScored;
        public event Action<int> OnAllCategoriesScored;
        public event Action<CategoryDefinition, ScoreResult> OnCategoryConfirmed;

        private readonly Dictionary<CategoryDefinition, int> scorecard = new();
        private readonly HashSet<CategoryDefinition> usedCategories = new();

        private GameEventBus eventBus;
        private ModifierRegistry modifierRegistry;
        private CategoryCatalog catalog;

        [Inject]
        public void Construct(GameEventBus eventBus, ModifierRegistry modifierRegistry, CategoryCatalog catalog)
        {
            this.eventBus = eventBus;
            this.modifierRegistry = modifierRegistry;
            this.catalog = catalog;
        }

        public CategoryCatalog Catalog => catalog;

        public bool IsCategoryUsed(CategoryDefinition category) => usedCategories.Contains(category);

        public int GetCategoryScore(CategoryDefinition category)
        {
            return scorecard.TryGetValue(category, out int score) ? score : -1;
        }

        public int TotalScore
        {
            get
            {
                int total = 0;
                foreach (var kvp in scorecard) total += kvp.Value;
                return total;
            }
        }

        public int CategoriesFilledCount => usedCategories.Count;

        public int TotalCategoryCount => catalog != null ? catalog.Count : 0;

        public bool IsComplete => CategoriesFilledCount >= TotalCategoryCount;

        public ScoreResult PreviewScore(IReadOnlyList<IDie> dice, CategoryDefinition category,
            int currentRoll = 0, int currentTurn = 0, int playerCurrency = 0)
        {
            int baseScore = category.Calculate(dice);

            if (eventBus == null || modifierRegistry == null)
                return ScoreResult.Create(baseScore, dice, category);

            var context = ModifierContext.CreateForScoring(
                baseScore, dice, category,
                currentRoll, currentTurn, playerCurrency,
                modifierRegistry.Active);

            eventBus.Fire(TriggerType.OnCategoryScored, context).Forget();

            return context.ToScoreResult();
        }

        public async UniTask<ScoreResult> ScoreCategoryAsync(IReadOnlyList<IDie> dice, CategoryDefinition category,
            int currentRoll, int currentTurn, int playerCurrency)
        {
            if (usedCategories.Contains(category))
                throw new InvalidOperationException($"Category {category.DisplayName} has already been scored.");

            int baseScore = category.Calculate(dice);

            ModifierContext context;
            if (eventBus != null && modifierRegistry != null)
            {
                context = ModifierContext.CreateForScoring(
                    baseScore, dice, category,
                    currentRoll, currentTurn, playerCurrency,
                    modifierRegistry.Active);

                await eventBus.Fire(TriggerType.OnCategoryScored, context);
            }
            else
            {
                context = new ModifierContext
                {
                    BaseScore = baseScore,
                    Dice = dice,
                    DiceValues = DiceCheckUtility.ExtractValues(dice),
                    Category = category,
                };
            }

            var result = context.ToScoreResult();
            int finalScore = result.FinalScore;
            scorecard[category] = finalScore;
            usedCategories.Add(category);

            OnCategoryScored?.Invoke(category, finalScore);
            OnCategoryConfirmed?.Invoke(category, result);

            if (IsComplete)
                OnAllCategoriesScored?.Invoke(TotalScore);

            return result;
        }

        public ScoreResult ScoreCategory(IReadOnlyList<IDie> dice, CategoryDefinition category)
        {
            if (usedCategories.Contains(category))
                throw new InvalidOperationException($"Category {category.DisplayName} has already been scored.");

            int baseScore = category.Calculate(dice);

            ModifierContext context = null;
            if (eventBus != null && modifierRegistry != null)
            {
                context = ModifierContext.CreateForScoring(
                    baseScore, dice, category,
                    0, 0, 0,
                    modifierRegistry.Active);

                eventBus.Fire(TriggerType.OnCategoryScored, context).Forget();
            }

            ScoreResult result;
            if (context != null)
                result = context.ToScoreResult();
            else
                result = ScoreResult.Create(baseScore, dice, category);

            int finalScore = result.FinalScore;
            scorecard[category] = finalScore;
            usedCategories.Add(category);

            OnCategoryScored?.Invoke(category, finalScore);
            OnCategoryConfirmed?.Invoke(category, result);

            if (IsComplete)
                OnAllCategoriesScored?.Invoke(TotalScore);

            return result;
        }

        public void ResetScorecard()
        {
            scorecard.Clear();
            usedCategories.Clear();
        }
    }
}
