using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine.TestTools;

namespace Jovian.TagSystem.Tests {
    public class TestJovianTagsHandler {
        private const string TagBase = "Test";
        private const string TagOne = "Test.One";
        private const string TagTwo = "Test.Two";
        private const string TagThree = "Test.Three";
        private const string TagFour = "Test.One.Four";

        private RegisteredTag[] tags;

        [SetUp]
        public void Setup() {
            tags = new RegisteredTag[] { new(TagOne), new(TagTwo), new(TagThree), new(TagFour), new(TagBase) };
            JovianTagsHandler.Initialize();
        }

        [TearDown]
        public void TearDown() {
            JovianTagsHandler.Reset();
        }

        [Test]
        public void GivenUnregisteredTag_WhenGetTag_ThenReturnsNone() {
            LogAssert.ignoreFailingMessages = true;
            var tag = JovianTagsHandler.GetTag(TagOne);
            Assert.IsTrue(tag.IsNone());
            LogAssert.ignoreFailingMessages = false;
        }

        [Test]
        public void GivenRegisteredTags_WhenGetTag_ThenReturnsValidTag() {
            JovianTagsHandler.RegisterTags(tags);
            var tagOne = JovianTagsHandler.GetTag(TagOne);
            var tagThree = JovianTagsHandler.GetTag(TagThree);
            Assert.IsTrue(tagOne.IsValid());
            Assert.IsTrue(tagThree.IsValid());
            Assert.AreNotEqual(tagOne.Id, tagThree.Id);
        }

        [Test]
        [TestCase(TagOne, ExpectedResult = true)]
        [TestCase(null, ExpectedResult = false)]
        [TestCase("", ExpectedResult = false)]
        [TestCase("None", ExpectedResult = true)]
        public bool GivenTryGetGameTag_ThenReturnsExpected(string tagName) {
            JovianTagsHandler.RegisterTags(tags);
            return JovianTagsHandler.TryGetGameTag(tagName, out _);
        }

        [Test]
        [TestCase(TagOne, ExpectedResult = true)]
        [TestCase(null, ExpectedResult = false)]
        [TestCase("", ExpectedResult = false)]
        [TestCase("None", ExpectedResult = false)]
        public bool GivenTryGetGameTagNotNone_ThenReturnsExpected(string tagName) {
            JovianTagsHandler.RegisterTags(tags);
            return JovianTagsHandler.TryGetGameTagThatIsNotNone(tagName, out _);
        }

        [Test]
        public void GivenRegisteredTag_WhenTryGet_ThenTagIsCorrect() {
            JovianTagsHandler.RegisterTags(tags);
            JovianTagsHandler.TryGetGameTag(TagOne, out var tag);
            Assert.AreEqual(JovianTagsHandler.GetTag(TagOne), tag);
        }

        [Test]
        public void GivenChildTag_WhenRegistered_ThenParentIdIsSet() {
            JovianTagsHandler.RegisterTags(tags);
            var tagFour = JovianTagsHandler.GetTag(TagFour);
            var tagOne = JovianTagsHandler.GetTag(TagOne);
            // TagFour is "Test.One.Four" — its parent should be "Test.One"
            Assert.AreEqual(tagOne.Id, tagFour.ParentId);
        }

        [Test]
        public void GivenChildTag_WhenChecked_ThenIsDescendantOfParent() {
            JovianTagsHandler.RegisterTags(tags);
            var tagBase = JovianTagsHandler.GetTag(TagBase);
            var tagOne = JovianTagsHandler.GetTag(TagOne);
            var tagFour = JovianTagsHandler.GetTag(TagFour);

            Assert.IsTrue(tagOne.IsDescendantOf(tagBase));
            Assert.IsTrue(tagFour.IsDescendantOf(tagBase));
            Assert.IsTrue(tagFour.IsDescendantOf(tagOne));
            Assert.IsFalse(tagBase.IsDescendantOf(tagOne));
        }

        [Test]
        public void GivenParentTag_WhenChecked_ThenIsAncestorOfChild() {
            JovianTagsHandler.RegisterTags(tags);
            var tagBase = JovianTagsHandler.GetTag(TagBase);
            var tagOne = JovianTagsHandler.GetTag(TagOne);
            var tagFour = JovianTagsHandler.GetTag(TagFour);

            Assert.IsTrue(tagBase.IsAncestorOf(tagOne));
            Assert.IsTrue(tagBase.IsAncestorOf(tagFour));
            Assert.IsTrue(tagOne.IsAncestorOf(tagFour));
            Assert.IsFalse(tagFour.IsAncestorOf(tagBase));
        }

        [Test]
        public void GivenSiblingTags_WhenChecked_ThenAreSiblings() {
            JovianTagsHandler.RegisterTags(tags);
            var tagOne = JovianTagsHandler.GetTag(TagOne);
            var tagTwo = JovianTagsHandler.GetTag(TagTwo);
            var tagThree = JovianTagsHandler.GetTag(TagThree);
            var tagFour = JovianTagsHandler.GetTag(TagFour);

            // One, Two, Three are all children of Test — siblings
            Assert.IsTrue(tagOne.IsSiblingTo(tagTwo));
            Assert.IsTrue(tagTwo.IsSiblingTo(tagThree));
            // Four is child of Test.One — not a sibling of Two
            Assert.IsFalse(tagFour.IsSiblingTo(tagTwo));
        }

        [Test]
        public void GivenRegisteredTag_WhenTagToString_ThenCorrectName() {
            JovianTagsHandler.RegisterTags(tags);
            var tagThree = JovianTagsHandler.GetTag(TagThree);
            Assert.AreEqual(TagThree, JovianTagsHandler.TagToString(tagThree));
        }

        [Test]
        public void GivenTagName_WhenDisplayName_ThenReturnsLastSegment() {
            Assert.AreEqual("Four", JovianTagsHandler.DisplayName(TagFour));
            Assert.AreEqual("Test", JovianTagsHandler.DisplayName(TagBase));
        }

        [Test]
        public void GivenRegisteredTags_WhenGetById_ThenReturnsCorrectTag() {
            JovianTagsHandler.RegisterTags(tags);
            var tagOne = JovianTagsHandler.GetTag(TagOne);
            var tagById = JovianTagsHandler.GetTag(tagOne.Id);
            Assert.AreEqual(tagOne, tagById);
        }

        [Test]
        public void GivenEmptyTag_WhenChecked_ThenIsNone() {
            Assert.IsTrue(JovianTagsHandler.emptyTag.IsNone());
            Assert.IsFalse(JovianTagsHandler.emptyTag.IsValid());
        }

        [Test]
        public void GivenSameTag_WhenRegisteredTwice_ThenSameId() {
            JovianTagsHandler.RegisterTag("Foo.Bar");
            var first = JovianTagsHandler.GetTag("Foo.Bar");
            JovianTagsHandler.RegisterTag("Foo.Bar");
            var second = JovianTagsHandler.GetTag("Foo.Bar");
            Assert.AreEqual(first.Id, second.Id);
        }
    }
}
