using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using YachtDice.Categories;
using YachtDice.Dice;
using YachtDice.Scoring;

namespace YachtDice.Tests
{
    public class ScoringSystemTests
    {
        private CategoryDefinition yachtCategory;
        private CategoryDefinition onesCategory;
        private CategoryDefinition twosCategory;
        private CategoryDefinition chanceCategory;
        private CategoryCatalog catalog;
        private DieDefinitionSO standardDie;

        [SetUp]
        public void SetUp()
        {
            standardDie = DieDefinitionSO.CreateForTest<StandardDieSO>("d6", "d6");

            yachtCategory = NOfAKindCategory.CreateForTest("yacht", "Яхта", 5, fixedScoreMode: true, score: 50);
            onesCategory = SumOfValueCategory.CreateForTest("ones", "Единицы", 1);
            twosCategory = SumOfValueCategory.CreateForTest("twos", "Двойки", 2);
            chanceCategory = SumAllCategory.CreateForTest("chance", "Шанс");

            catalog = CategoryCatalog.CreateForTest(new List<CategoryDefinition>
            {
                onesCategory, twosCategory, yachtCategory, chanceCategory
            });
        }

        [TearDown]
        public void TearDown()
        {
            foreach (var go in Object.FindObjectsByType<ScoringSystem>(FindObjectsSortMode.None))
                Object.DestroyImmediate(go.gameObject);

            Object.DestroyImmediate(yachtCategory);
            Object.DestroyImmediate(onesCategory);
            Object.DestroyImmediate(twosCategory);
            Object.DestroyImmediate(chanceCategory);
            Object.DestroyImmediate(catalog);
            Object.DestroyImmediate(standardDie);
        }

        private ScoringSystem CreateScoringSystem()
        {
            var go = new GameObject("ScoringSystem");
            return go.AddComponent<ScoringSystem>();
        }

        private IReadOnlyList<IDie> CreateDice(params int[] values)
        {
            var dice = new DieInstance[values.Length];
            for (int i = 0; i < values.Length; i++)
                dice[i] = new DieInstance(standardDie, values[i]);
            return dice;
        }

        [Test]
        public void ScoreCategory_WithNoModifiers_CalculatesBaseOnly()
        {
            var system = CreateScoringSystem();
            var dice = CreateDice(6, 6, 6, 6, 6);
            var result = system.ScoreCategory(dice, yachtCategory);

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

        [Test]
        public void ScoreCategory_FiresOnCategoryConfirmed()
        {
            var system = CreateScoringSystem();
            CategoryDefinition firedCategory = null;
            ScoreResult firedResult = default;

            system.OnCategoryConfirmed += (cat, res) =>
            {
                firedCategory = cat;
                firedResult = res;
            };

            var dice = CreateDice(1, 1, 1, 1, 1);
            system.ScoreCategory(dice, onesCategory);

            Assert.AreEqual(onesCategory, firedCategory);
            Assert.AreEqual(5, firedResult.BaseScore);
        }

        [Test]
        public void ScoreCategory_PreventsDuplicateCategory()
        {
            var system = CreateScoringSystem();
            var dice = CreateDice(1, 2, 3, 4, 5);
            system.ScoreCategory(dice, chanceCategory);

            Assert.Throws<System.InvalidOperationException>(() =>
                system.ScoreCategory(dice, chanceCategory));
        }

        [Test]
        public void PreviewScore_WithNoModifiers_CalculatesBaseOnly()
        {
            var system = CreateScoringSystem();
            var dice = CreateDice(1, 2, 3, 4, 5);
            var result = system.PreviewScore(dice, chanceCategory);

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

        [Test]
        public void TotalScore_SumsAllScoredCategories()
        {
            var system = CreateScoringSystem();
            system.ScoreCategory(CreateDice(1, 1, 1, 1, 1), onesCategory);
            system.ScoreCategory(CreateDice(2, 2, 2, 2, 2), twosCategory);

            Assert.AreEqual(15, system.TotalScore); // 5 + 10
        }

        [Test]
        public void ResetScorecard_ClearsAll()
        {
            var system = CreateScoringSystem();
            system.ScoreCategory(CreateDice(1, 1, 1, 1, 1), onesCategory);

            system.ResetScorecard();

            Assert.AreEqual(0, system.TotalScore);
            Assert.IsFalse(system.IsCategoryUsed(onesCategory));
        }

        // ── Category SO Unit Tests ──────────────────────────────────

        [Test]
        public void SumOfValueCategory_SumsCorrectly()
        {
            var dice = CreateDice(3, 3, 3, 1, 2);
            var cat = SumOfValueCategory.CreateForTest("threes", "Тройки", 3);

            Assert.AreEqual(9, cat.Calculate(dice));

            Object.DestroyImmediate(cat);
        }

        [Test]
        public void NOfAKindCategory_ThreeOfAKind_ReturnsSumOrZero()
        {
            var cat = NOfAKindCategory.CreateForTest("three_of_a_kind", "Тройка", 3);

            Assert.AreEqual(17, cat.Calculate(CreateDice(4, 4, 4, 3, 2))); // sum = 17
            Assert.AreEqual(0, cat.Calculate(CreateDice(1, 2, 3, 4, 5)));  // no 3-of-a-kind

            Object.DestroyImmediate(cat);
        }

        [Test]
        public void NOfAKindCategory_Yacht_ReturnsFixedScore()
        {
            Assert.AreEqual(50, yachtCategory.Calculate(CreateDice(6, 6, 6, 6, 6)));
            Assert.AreEqual(0, yachtCategory.Calculate(CreateDice(6, 6, 6, 6, 1)));
        }

        [Test]
        public void FullHouseCategory_CalculatesCorrectly()
        {
            var cat = FullHouseCategory.CreateForTest("fh", "Фулл-хаус", 25);

            Assert.AreEqual(25, cat.Calculate(CreateDice(3, 3, 3, 2, 2)));
            Assert.AreEqual(0, cat.Calculate(CreateDice(3, 3, 3, 3, 2)));

            Object.DestroyImmediate(cat);
        }

        [Test]
        public void StraightCategory_SmallStraight()
        {
            var cat = StraightCategory.CreateForTest("ss", "Малый стрит", 4, 30);

            Assert.AreEqual(30, cat.Calculate(CreateDice(1, 2, 3, 4, 6)));
            Assert.AreEqual(0, cat.Calculate(CreateDice(1, 2, 3, 5, 6)));

            Object.DestroyImmediate(cat);
        }

        [Test]
        public void StraightCategory_LargeStraight()
        {
            var cat = StraightCategory.CreateForTest("ls", "Большой стрит", 5, 40);

            Assert.AreEqual(40, cat.Calculate(CreateDice(1, 2, 3, 4, 5)));
            Assert.AreEqual(40, cat.Calculate(CreateDice(2, 3, 4, 5, 6)));
            Assert.AreEqual(0, cat.Calculate(CreateDice(1, 2, 3, 4, 6)));

            Object.DestroyImmediate(cat);
        }

        [Test]
        public void SumAllCategory_SumsEverything()
        {
            Assert.AreEqual(15, chanceCategory.Calculate(CreateDice(1, 2, 3, 4, 5)));
            Assert.AreEqual(30, chanceCategory.Calculate(CreateDice(6, 6, 6, 6, 6)));
        }
    }
}
