using Engine.Scene;

namespace EngineTests.Scene;

public class HierarchyTests
{
  private Hierarchy<object> _hierarchy;

  [SetUp]
  public void Setup()
  {
    _hierarchy = new Hierarchy<object>();
  }

  [Test]
  public void AddToRoot()
  {
    var obj = new object();
    _hierarchy.Add(obj);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.GetChildren().Count(), Is.EqualTo(1));
      Assert.That(_hierarchy.GetParent(obj), Is.Null);
    });
  }

  [Test]
  public void AddToChild()
  {
    var parent = new object();
    var child = new object();

    _hierarchy.Add(parent);
    _hierarchy.Add(child);

    _hierarchy.AddChild(parent, child);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.GetChildren(parent).Count(), Is.EqualTo(1));
      Assert.That(_hierarchy.GetParent(parent), Is.Null);
      Assert.That(_hierarchy.GetParent(child), Is.EqualTo(parent));
    });
  }

  [Test]
  public void RemoveChild()
  {
    var parent = new object();
    var child = new object();

    _hierarchy.Add(parent);
    _hierarchy.Add(child);

    _hierarchy.AddChild(parent, child);
    _hierarchy.ProcessChanges();

    _hierarchy.Remove(child);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.GetChildren(parent).Count(), Is.EqualTo(0));
      Assert.That(_hierarchy.Contains(child), Is.False);
    });
  }

  [Test]
  public void RemoveParentAndAllChildren()
  {
    var parent = new object();
    var child1 = new object();
    var child2 = new object();

    _hierarchy.Add(parent);
    _hierarchy.Add(child1);
    _hierarchy.Add(child2);

    _hierarchy.AddChild(parent, child1);
    _hierarchy.AddChild(parent, child2);
    _hierarchy.ProcessChanges();

    _hierarchy.Remove(parent);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.GetChildren().Count(), Is.EqualTo(0));
      Assert.That(_hierarchy.Contains(child1), Is.False);
      Assert.That(_hierarchy.Contains(child2), Is.False);
      Assert.That(_hierarchy.Contains(parent), Is.False);
    });
  }

  [Test]
  public void DetectCycleInHierarchy()
  {
    var parent = new object();
    var child = new object();

    _hierarchy.Add(parent);
    _hierarchy.AddChild(parent, child);
    _hierarchy.AddChild(child, parent);

    Assert.Throws<InvalidOperationException>(() => _hierarchy.ProcessChanges());
  }

  [Test]
  public void IsInHierarchy_PositiveCase()
  {
    var ancestor = new object();
    var child = new object();

    _hierarchy.Add(ancestor);
    _hierarchy.Add(child);

    _hierarchy.AddChild(ancestor, child);
    _hierarchy.ProcessChanges();

    Assert.That(_hierarchy.IsInHierarchy(ancestor, child), Is.True);
  }

  [Test]
  public void IsInHierarchy_NegativeCase()
  {
    var obj1 = new object();
    var obj2 = new object();

    _hierarchy.Add(obj1);
    _hierarchy.Add(obj2);
    _hierarchy.ProcessChanges();

    Assert.That(_hierarchy.IsInHierarchy(obj1, obj2), Is.False);
  }

  [Test]
  public void MultipleChildrenHandling()
  {
    var parent = new object();
    var child1 = new object();
    var child2 = new object();

    _hierarchy.Add(parent);
    _hierarchy.Add(child1);
    _hierarchy.Add(child2);

    _hierarchy.AddChild(parent, child1);
    _hierarchy.AddChild(parent, child2);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.GetChildren(parent).Count(), Is.EqualTo(2));
      Assert.That(_hierarchy.GetParent(child1), Is.EqualTo(parent));
      Assert.That(_hierarchy.GetParent(child2), Is.EqualTo(parent));
    });
  }

  [Test]
  public void GetChildren_EmptyCase()
  {
    var obj = new object();
    _hierarchy.Add(obj);
    _hierarchy.ProcessChanges();

    Assert.That(_hierarchy.GetChildren(obj).Count(), Is.EqualTo(0));
  }

  // [Test]
  // public void AddDuplicateObjectThrows()
  // {
  //   var obj = new object();
  //
  //   _hierarchy.Add(obj);
  //   _hierarchy.Add(obj);
  //
  //   Assert.Throws<ArgumentException>(() => _hierarchy.ProcessChanges());
  // }

  [Test]
  public void AddChild_ParentNotInHierarchyThrows()
  {
    var parent = new object();
    var child = new object();

    _hierarchy.Add(child);
    _hierarchy.AddChild(parent, child);

    Assert.Throws<InvalidOperationException>(() => _hierarchy.ProcessChanges());
  }

  [Test]
  public void GetParent_ObjectNotInHierarchyThrows()
  {
    var obj = new object();

    Assert.That(_hierarchy.Contains(obj), Is.False);
  }

  [Test]
  public void NestedHierarchyTest()
  {
    var grandParent = new object();
    var parent = new object();
    var child = new object();

    _hierarchy.Add(grandParent);
    _hierarchy.Add(parent);
    _hierarchy.Add(child);

    _hierarchy.AddChild(grandParent, parent);
    _hierarchy.AddChild(parent, child);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.GetParent(parent), Is.EqualTo(grandParent));
      Assert.That(_hierarchy.GetParent(child), Is.EqualTo(parent));
      Assert.That(_hierarchy.IsInHierarchy(grandParent, child), Is.True);
    });
  }

  [Test]
  public void ReparentObjectTest()
  {
    var parent1 = new object();
    var parent2 = new object();
    var child = new object();

    _hierarchy.Add(parent1);
    _hierarchy.Add(parent2);
    _hierarchy.Add(child);

    _hierarchy.AddChild(parent1, child);
    _hierarchy.ProcessChanges();

    _hierarchy.AddChild(parent2, child);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.GetChildren(parent1).Count(), Is.EqualTo(0));
      Assert.That(_hierarchy.GetChildren(parent2).Count(), Is.EqualTo(1));
      Assert.That(_hierarchy.GetParent(child), Is.EqualTo(parent2));
    });
  }

  [Test]
  public void RemoveRootObjectsTest()
  {
    var obj1 = new object();
    var obj2 = new object();

    _hierarchy.Add(obj1);
    _hierarchy.Add(obj2);
    _hierarchy.ProcessChanges();

    _hierarchy.Remove(obj1);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.GetChildren().Count(), Is.EqualTo(1));
      Assert.That(_hierarchy.Contains(obj1), Is.False);
    });
  }

  [Test]
  public void IsInHierarchy_SelfTest()
  {
    var obj = new object();
    _hierarchy.Add(obj);
    _hierarchy.ProcessChanges();

    Assert.That(_hierarchy.IsInHierarchy(obj, obj), Is.True);
  }

  [Test]
  public void ComplexHierarchyRemovalTest()
  {
    var grandParent = new object();
    var parent1 = new object();
    var parent2 = new object();
    var child1 = new object();
    var child2 = new object();

    _hierarchy.Add(grandParent);
    _hierarchy.Add(parent1);
    _hierarchy.Add(parent2);
    _hierarchy.Add(child1);
    _hierarchy.Add(child2);

    _hierarchy.AddChild(grandParent, parent1);
    _hierarchy.AddChild(grandParent, parent2);
    _hierarchy.AddChild(parent1, child1);
    _hierarchy.AddChild(parent2, child2);
    _hierarchy.ProcessChanges();

    _hierarchy.Remove(parent1);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.GetChildren(grandParent).Count(), Is.EqualTo(1));
      Assert.That(_hierarchy.Contains(child1), Is.False);
      Assert.That(_hierarchy.Contains(parent1), Is.False);
    });
  }

  [Test]
  public void AddChild_SelfAsParentThrowsException()
  {
    var obj = new object();
    _hierarchy.Add(obj);

    Assert.Throws<InvalidOperationException>(() => _hierarchy.AddChild(obj, obj));
  }

  [Test]
  public void MultiLevelReparentingTest()
  {
    var grandParent = new object();
    var parent = new object();
    var child1 = new object();
    var child2 = new object();

    _hierarchy.Add(grandParent);
    _hierarchy.Add(parent);
    _hierarchy.Add(child1);
    _hierarchy.Add(child2);

    _hierarchy.AddChild(grandParent, parent);
    _hierarchy.AddChild(parent, child1);
    _hierarchy.AddChild(parent, child2);
    _hierarchy.ProcessChanges();

    _hierarchy.AddChild(child2, child1);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.GetChildren(parent).Count(), Is.EqualTo(1));
      Assert.That(_hierarchy.GetParent(child1), Is.EqualTo(child2));
      Assert.That(_hierarchy.IsInHierarchy(grandParent, child1), Is.True);
    });
  }

  [Test]
  public void DeepHierarchyTest()
  {
    const int hierarchyDepth = 100;
    var objects = new object[hierarchyDepth];

    for (var i = 0; i < hierarchyDepth; i++)
    {
      objects[i] = new object();
      _hierarchy.Add(objects[i]);
    }

    for (var i = 1; i < hierarchyDepth; i++)
    {
      _hierarchy.AddChild(objects[i - 1], objects[i]);
    }

    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      for (var i = 1; i < hierarchyDepth; i++)
      {
        Assert.That(_hierarchy.GetParent(objects[i]), Is.EqualTo(objects[i - 1]));
        Assert.That(_hierarchy.IsInHierarchy(objects[0], objects[i]), Is.True);
      }

      for (var i = 0; i < hierarchyDepth - 1; i++)
      {
        var children = _hierarchy.GetChildren(objects[i]).ToList();
        Assert.That(children, Has.Count.EqualTo(1));
        Assert.That(children[0], Is.EqualTo(objects[i + 1]));
      }
    });
  }

  [Test]
  public void RemoveFromMiddleOfHierarchyTest()
  {
    var grandParent = new object();
    var parent = new object();
    var child1 = new object();
    var child2 = new object();

    _hierarchy.Add(grandParent);
    _hierarchy.Add(parent);
    _hierarchy.Add(child1);
    _hierarchy.Add(child2);

    _hierarchy.AddChild(grandParent, parent);
    _hierarchy.AddChild(parent, child1);
    _hierarchy.AddChild(parent, child2);
    _hierarchy.ProcessChanges();

    _hierarchy.Remove(parent);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.GetChildren(grandParent).Count(), Is.EqualTo(0));
      Assert.That(_hierarchy.Contains(child1), Is.False);
      Assert.That(_hierarchy.Contains(child2), Is.False);
      Assert.That(_hierarchy.Contains(parent), Is.False);
    });
  }

  [Test]
  public void ConcurrentModificationTest()
  {
    var parent = new object();
    var children = Enumerable.Range(0, 10)
      .Select(_ => new object())
      .ToList();

    _hierarchy.Add(parent);
    children.ForEach(parChild => _hierarchy.Add(parChild));

    children.AsParallel().ForAll(parChild => { _hierarchy.AddChild(parent, parChild); });

    _hierarchy.ProcessChanges();

    Assert.That(_hierarchy.GetChildren(parent).Count(), Is.EqualTo(children.Count));
  }

  [Test]
  public void IsInHierarchy_NullInputs()
  {
    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.IsInHierarchy(new object(), null), Is.False);
      Assert.That(_hierarchy.IsInHierarchy(null, new object()), Is.False);
      Assert.That(_hierarchy.IsInHierarchy(null, null), Is.False);
    });
  }

  [Test]
  public void IsInHierarchy_SameObject()
  {
    var obj = new object();
    _hierarchy.Add(obj);
    _hierarchy.ProcessChanges();

    Assert.That(_hierarchy.IsInHierarchy(obj, obj), Is.True);
  }

  [Test]
  public void IsInHierarchy_DirectParentChild()
  {
    var parent = new object();
    var child = new object();

    _hierarchy.Add(parent);
    _hierarchy.Add(child);
    _hierarchy.AddChild(parent, child);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.IsInHierarchy(parent, child), Is.True);
      Assert.That(_hierarchy.IsInHierarchy(child, parent), Is.False);
    });
  }

  [Test]
  public void IsInHierarchy_DeepHierarchy()
  {
    var grandParent = new object();
    var parent = new object();
    var child = new object();
    var grandChild = new object();

    _hierarchy.Add(grandParent);
    _hierarchy.Add(parent);
    _hierarchy.Add(child);
    _hierarchy.Add(grandChild);

    _hierarchy.AddChild(grandParent, parent);
    _hierarchy.AddChild(parent, child);
    _hierarchy.AddChild(child, grandChild);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.IsInHierarchy(grandParent, grandChild), Is.True);
      Assert.That(_hierarchy.IsInHierarchy(parent, grandChild), Is.True);
      Assert.That(_hierarchy.IsInHierarchy(child, grandChild), Is.True);

      Assert.That(_hierarchy.IsInHierarchy(grandChild, grandParent), Is.False);
      Assert.That(_hierarchy.IsInHierarchy(grandChild, parent), Is.False);
      Assert.That(_hierarchy.IsInHierarchy(grandChild, child), Is.False);
    });
  }

  [Test]
  public void IsInHierarchy_SeparateBranches()
  {
    var root = new object();
    var branch1Parent = new object();
    var branch1Child = new object();
    var branch2Parent = new object();
    var branch2Child = new object();

    _hierarchy.Add(root);
    _hierarchy.Add(branch1Parent);
    _hierarchy.Add(branch1Child);
    _hierarchy.Add(branch2Parent);
    _hierarchy.Add(branch2Child);

    _hierarchy.AddChild(root, branch1Parent);
    _hierarchy.AddChild(root, branch2Parent);
    _hierarchy.AddChild(branch1Parent, branch1Child);
    _hierarchy.AddChild(branch2Parent, branch2Child);
    _hierarchy.ProcessChanges();

    Assert.Multiple(() =>
    {
      Assert.That(_hierarchy.IsInHierarchy(root, branch1Parent), Is.True);
      Assert.That(_hierarchy.IsInHierarchy(root, branch1Child), Is.True);
      Assert.That(_hierarchy.IsInHierarchy(branch1Parent, branch1Child), Is.True);

      Assert.That(_hierarchy.IsInHierarchy(branch1Parent, branch2Child), Is.False);
      Assert.That(_hierarchy.IsInHierarchy(branch2Parent, branch1Child), Is.False);
    });
  }

  [Test]
  public void IsInHierarchy_ObjectsNotInHierarchy()
  {
    var obj1 = new object();
    var obj2 = new object();

    _hierarchy.Add(obj1);
    _hierarchy.Add(obj2);
    _hierarchy.ProcessChanges();

    Assert.That(_hierarchy.IsInHierarchy(obj1, obj2), Is.False);
  }
}