using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using YachtDice.Categories;
using YachtDice.Modifiers.Conditions;
using YachtDice.Modifiers.Core;
using YachtDice.Modifiers.Definition;
using YachtDice.Modifiers.Effects;
using YachtDice.Modifiers.Pipeline;
using YachtDice.Modifiers.Runtime;
using YachtDice.Scoring;

namespace YachtDice.Tests
{
    public class ModifierPipelineTests
    {
        private ModifierRegistry registry;
        private ModifierPipeline pipeline;

        // Тестовые категории
        private CategoryDefinition chanceCategory;
        private CategoryDefinition fullHouseCategory;
        private CategoryDefinition onesCategory;
        private CategoryDefinition threesCategory;
        private CategoryDefinition foursCategory;
        private CategoryDefinition sixesCategory;

        [SetUp]
        public void SetUp()
        {
            registry = new ModifierRegistry(10);
            pipeline = new ModifierPipeline(registry);
            pipeline.TracingEnabled = false;

            chanceCategory = SumAllCategory.CreateForTest("chance", "Шанс");
            fullHouseCategory = FullHouseCategory.CreateForTest("full_house", "Фулл-хаус");
            onesCategory = SumOfValueCategory.CreateForTest("ones", "Единицы", 1);
            threesCategory = SumOfValueCategory.CreateForTest("threes", "Тройки", 3);
            foursCategory = SumOfValueCategory.CreateForTest("fours", "Четвёрки", 4);
            sixesCategory = SumOfValueCategory.CreateForTest("sixes", "Шестёрки", 6);
        }

        [TearDown]
        public void TearDown()
        {
            Object.DestroyImmediate(chanceCategory);
            Object.DestroyImmediate(fullHouseCategory);
            Object.DestroyImmediate(onesCategory);
            Object.DestroyImmediate(threesCategory);
            Object.DestroyImmediate(foursCategory);
            Object.DestroyImmediate(sixesCategory);
        }

        private ModifierDefinition CreateDef(string id,
            TriggerType trigger,
            List<Condition> conditions,
            List<Effect> effects)
        {
            var behavior = ModifierBehavior.CreateForTest(trigger, conditions, effects);
            return ModifierDefinition.CreateForTest(id,
                new List<ModifierBehavior> { behavior });
        }

        private void RegisterAndActivate(ModifierDefinition def)
        {
            var inst = registry.Add(def);
            registry.TryActivate(inst);
        }

        private ModifierContext CreateScoringContext(int baseScore, int[] dice, CategoryDefinition category)
        {
            return new ModifierContext
            {
                BaseScore = baseScore,
                DiceValues = dice,
                Category = category,
                AllActiveModifiers = registry.Active,
            };
        }

        // ── Phase Ordering ──────────────────────────────────────────

        [Test]
        public void Execute_AdditiveBeforeMultiplicative()
        {
            var addEffect = AddFlatScoreEffect.CreateForTest(10, ModifierPhase.Additive);
            var mulEffect = MultiplyScoreEffect.CreateForTest(2f, ModifierPhase.Multiplicative);

            var addDef = CreateDef("add", TriggerType.OnCategoryScored, null,
                new List<Effect> { addEffect });
            var mulDef = CreateDef("mul", TriggerType.OnCategoryScored, null,
                new List<Effect> { mulEffect });

            RegisterAndActivate(mulDef);
            RegisterAndActivate(addDef);

            var ctx = CreateScoringContext(20, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            // (20 + 10) * 2 = 60
            Assert.AreEqual(60, result.FinalScore);
        }

        [Test]
        public void Execute_PostMultiplicativeAfterMultiplicative()
        {
            var mulEffect = MultiplyScoreEffect.CreateForTest(2f, ModifierPhase.Multiplicative);
            var postMulEffect = PostMultiplyEffect.CreateForTest(3f, ModifierPhase.PostMultiplicative);

            var mulDef = CreateDef("mul", TriggerType.OnCategoryScored, null,
                new List<Effect> { mulEffect });
            var postDef = CreateDef("post", TriggerType.OnCategoryScored, null,
                new List<Effect> { postMulEffect });

            RegisterAndActivate(postDef);
            RegisterAndActivate(mulDef);

            var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            // (10 + 0) * 2 * 3 = 60
            Assert.AreEqual(60, result.FinalScore);
        }

        // ── Condition Filtering ─────────────────────────────────────

        [Test]
        public void Execute_ConditionFails_SkipsEffect()
        {
            var condition = CategoryCondition.CreateForTest(fullHouseCategory);
            var effect = AddFlatScoreEffect.CreateForTest(100);

            var def = CreateDef("fh-bonus", TriggerType.OnCategoryScored,
                new List<Condition> { condition },
                new List<Effect> { effect });

            RegisterAndActivate(def);

            // Scoring Ones, not FullHouse — condition should fail
            var ctx = CreateScoringContext(5, new[] { 1, 1, 1, 1, 1 }, onesCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            Assert.AreEqual(0, result.FlatBonus);
            Assert.AreEqual(5, result.FinalScore);
        }

        [Test]
        public void Execute_ConditionPasses_AppliesEffect()
        {
            var condition = CategoryCondition.CreateForTest(fullHouseCategory);
            var effect = AddFlatScoreEffect.CreateForTest(15);

            var def = CreateDef("fh-bonus", TriggerType.OnCategoryScored,
                new List<Condition> { condition },
                new List<Effect> { effect });

            RegisterAndActivate(def);

            var ctx = CreateScoringContext(25, new[] { 3, 3, 3, 2, 2 }, fullHouseCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            Assert.AreEqual(15, result.FlatBonus);
            Assert.AreEqual(40, result.FinalScore);
        }

        // ── Trigger Filtering ───────────────────────────────────────

        [Test]
        public void Execute_WrongTrigger_SkipsModifier()
        {
            var effect = AddFlatScoreEffect.CreateForTest(999);
            var def = CreateDef("turn-bonus", TriggerType.OnTurnStart, null,
                new List<Effect> { effect });

            RegisterAndActivate(def);

            var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            Assert.AreEqual(0, result.FlatBonus);
            Assert.AreEqual(10, result.FinalScore);
        }

        // ── Multiple Modifiers ──────────────────────────────────────

        [Test]
        public void Execute_MultipleModifiers_CorrectOrder()
        {
            var perDieAdd = AddPerDieEffect.CreateForTest(2, targetDieValue: 3, phase: ModifierPhase.Additive);
            var perDieMul = MultiplyPerDieEffect.CreateForTest(1.5f, targetDieValue: 3, phase: ModifierPhase.Multiplicative);
            var flatAdd = AddFlatScoreEffect.CreateForTest(10, ModifierPhase.Additive, priority: 10);
            var finalMul = MultiplyScoreEffect.CreateForTest(2f, ModifierPhase.Multiplicative, priority: 10);

            var def1 = CreateDef("pda", TriggerType.OnCategoryScored, null,
                new List<Effect> { perDieAdd });
            var def2 = CreateDef("pdm", TriggerType.OnCategoryScored, null,
                new List<Effect> { perDieMul });
            var def3 = CreateDef("fa", TriggerType.OnCategoryScored, null,
                new List<Effect> { flatAdd });
            var def4 = CreateDef("fm", TriggerType.OnCategoryScored, null,
                new List<Effect> { finalMul });

            RegisterAndActivate(def4);
            RegisterAndActivate(def3);
            RegisterAndActivate(def2);
            RegisterAndActivate(def1);

            // dice: [3, 3, 3, 1, 2] — 3 threes
            var ctx = CreateScoringContext(9, new[] { 3, 3, 3, 1, 2 }, threesCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            Assert.AreEqual(16, result.FlatBonus);
            Assert.AreEqual(168, result.FinalScore);
        }

        // ── Empty / Null Cases ──────────────────────────────────────

        [Test]
        public void Execute_NoActiveModifiers_NoChange()
        {
            var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            Assert.AreEqual(10, result.FinalScore);
            Assert.AreEqual(0, result.FlatBonus);
            Assert.AreEqual(1f, result.Multiplier);
        }

        [Test]
        public void Execute_InactiveModifier_Skipped()
        {
            var effect = AddFlatScoreEffect.CreateForTest(50);
            var def = CreateDef("inactive", TriggerType.OnCategoryScored, null,
                new List<Effect> { effect });

            registry.Add(def);

            var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            Assert.AreEqual(0, result.FlatBonus);
            Assert.AreEqual(10, result.FinalScore);
        }

        // ── Side Effects ────────────────────────────────────────────

        [Test]
        public void Execute_SideEffectsInSideEffectPhase()
        {
            var scoreEffect = AddFlatScoreEffect.CreateForTest(10, ModifierPhase.Additive);
            var currencyEffect = AddCurrencyEffect.CreateForTest(25, ModifierPhase.SideEffect);

            var def = CreateDef("rewards", TriggerType.OnCategoryScored, null,
                new List<Effect> { scoreEffect, currencyEffect });

            RegisterAndActivate(def);

            var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            Assert.AreEqual(10, result.FlatBonus);
            Assert.AreEqual(25, result.CurrencyDelta);
            Assert.AreEqual(20, result.FinalScore);
        }

        // ── Tracing ─────────────────────────────────────────────────

        [Test]
        public void Execute_TracingEnabled_PopulatesDebugLog()
        {
            pipeline.TracingEnabled = true;

            var effect = AddFlatScoreEffect.CreateForTest(10);
            var def = CreateDef("traced", TriggerType.OnCategoryScored, null,
                new List<Effect> { effect });

            RegisterAndActivate(def);

            var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            Assert.IsNotNull(result.DebugLog);
            Assert.IsTrue(result.DebugLog.Count > 0);
        }

        // ── DieValue Condition ──────────────────────────────────────

        [Test]
        public void Execute_DieValueCondition_OnlyTriggersOnMatch()
        {
            var condition = DieValueCondition.CreateForTest(6, minCount: 3);
            var effect = AddFlatScoreEffect.CreateForTest(100);

            var def = CreateDef("sixes-bonus", TriggerType.OnCategoryScored,
                new List<Condition> { condition },
                new List<Effect> { effect });

            RegisterAndActivate(def);

            // Only 2 sixes — condition requires 3
            var ctx = CreateScoringContext(12, new[] { 6, 6, 1, 2, 3 }, sixesCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            Assert.AreEqual(0, result.FlatBonus);

            // 3 sixes — condition passes
            var ctx2 = CreateScoringContext(18, new[] { 6, 6, 6, 1, 2 }, sixesCategory);
            var result2 = pipeline.Execute(TriggerType.OnCategoryScored, ctx2).GetAwaiter().GetResult();

            Assert.AreEqual(100, result2.FlatBonus);
        }

        // ── MinScore Condition ──────────────────────────────────────

        [Test]
        public void Execute_MinScoreCondition_ThresholdWorks()
        {
            var condition = MinScoreCondition.CreateForTest(20);
            var effect = MultiplyScoreEffect.CreateForTest(2f);

            var def = CreateDef("high-score-bonus", TriggerType.OnCategoryScored,
                new List<Condition> { condition },
                new List<Effect> { effect });

            RegisterAndActivate(def);

            // Below threshold
            var ctx = CreateScoringContext(15, new[] { 3, 3, 3, 3, 3 }, threesCategory);
            var result = pipeline.Execute(TriggerType.OnCategoryScored, ctx).GetAwaiter().GetResult();

            Assert.AreEqual(1f, result.Multiplier);

            // At threshold
            var ctx2 = CreateScoringContext(20, new[] { 4, 4, 4, 4, 4 }, foursCategory);
            var result2 = pipeline.Execute(TriggerType.OnCategoryScored, ctx2).GetAwaiter().GetResult();

            Assert.AreEqual(2f, result2.Multiplier);
        }

        // ── ToScoreResult ───────────────────────────────────────────

        [Test]
        public void ToScoreResult_ConvertsCorrectly()
        {
            var ctx = CreateScoringContext(10, new[] { 1, 2, 3, 4, 5 }, chanceCategory);
            ctx.FlatBonus = 5;
            ctx.Multiplier = 2f;
            ctx.PostMultiplier = 1.5f;

            ScoreResult sr = ctx.ToScoreResult();

            Assert.AreEqual(10, sr.BaseScore);
            Assert.AreEqual(5, sr.FlatBonus);
            Assert.AreEqual(3f, sr.Multiplier, 0.001f); // 2 * 1.5
            Assert.AreEqual(chanceCategory, sr.Category);
        }
    }
}
