using UnityEngine;
using NUnit.Framework;
using LLM.Editor.Data;
using LLM.Editor.Helper;
using LLM.Editor.Commands;
using System.Collections.Generic;

namespace LLM.Editor.Tests.Unit
{
    [TestFixture]
    public class CreateGameObjectCommandTests
    {
        private CommandContext _context;
        private List<GameObject> _objectsToCleanUp;

        [SetUp]
        public void SetUp()
        {
            _context = new CommandContext();
            _objectsToCleanUp = new List<GameObject>();
        }

        [TearDown]
        public void TearDown()
        {
            foreach (var obj in _objectsToCleanUp)
            {
                if (obj != null)
                {
                    Object.DestroyImmediate(obj);
                }
            }
        }

        private GameObject FindObjectInScene(string name)
        {
            var obj = GameObject.Find(name);
            if (obj != null)
            {
                _objectsToCleanUp.Add(obj);
            }
            return obj;
        }

        [Test]
        public void Execute_WithValidName_CreatesEmptyGameObject()
        {
            // Arrange
            var parameters = new CreateGameObjectParams { gameObjectName = "MyEmptyObject", logicalName = "TestEmpty_1" };
            var command = new CreateGameObjectCommand(parameters.ToJson());

            // Act
            var outcome = command.Execute(_context);
            var createdObject = FindObjectInScene("MyEmptyObject");

            // Assert
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.IsNotNull(createdObject, "GameObject should be created in the scene.");
            Assert.AreEqual("MyEmptyObject", createdObject.name);
            Assert.AreEqual("TestEmpty_1", _context.CurrentSubject, "Context's current subject should be the logical name.");
            Assert.IsTrue(_context.IdentifierMap.ContainsKey("TestEmpty_1"), "Identifier map should contain the logical name.");
        }

        [Test]
        public void Execute_WithValidPrimitiveType_CreatesPrimitive()
        {
            // Arrange
            var parameters = new CreateGameObjectParams { gameObjectName = "MyCube", logicalName = "TestCube_1", primitiveType = "Cube" };
            var command = new CreateGameObjectCommand(parameters.ToJson());

            // Act
            var outcome = command.Execute(_context);
            var createdObject = FindObjectInScene("MyCube");

            // Assert
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.IsNotNull(createdObject);
            Assert.AreEqual("MyCube", createdObject.name);
            Assert.IsNotNull(createdObject.GetComponent<MeshFilter>(), "Created object should have a MeshFilter component.");
            Assert.AreEqual("TestCube_1", _context.CurrentSubject);
        }

        [Test]
        public void Execute_WithInvalidPrimitiveType_ReturnsError()
        {
            // Arrange
            var parameters = new CreateGameObjectParams { gameObjectName = "MyInvalid", logicalName = "TestInvalid_1", primitiveType = "InvalidShape" };
            var command = new CreateGameObjectCommand(parameters.ToJson());
            
            // Act
            var outcome = command.Execute(_context);
            var createdObject = FindObjectInScene("MyInvalid");

            // Assert
            Assert.AreEqual(CommandOutcome.Error, outcome);
            Assert.IsNull(createdObject, "No GameObject should be created on error.");
            Assert.IsNotNull(_context.ErrorMessage);
            Assert.IsTrue(_context.ErrorMessage.Contains("Invalid primitive type"));
        }

        [Test]
        public void Execute_WithMissingGameObjectName_ReturnsError()
        {
            // Arrange
            var parameters = new CreateGameObjectParams { gameObjectName = "", logicalName = "TestMissingName_1" };
            var command = new CreateGameObjectCommand(parameters.ToJson());

            // Act
            var outcome = command.Execute(_context);

            // Assert
            Assert.AreEqual(CommandOutcome.Error, outcome);
            Assert.IsNotNull(_context.ErrorMessage);
            Assert.IsTrue(_context.ErrorMessage.Contains("A name for the GameObject is required"));
        }
        
        [Test]
        public void Execute_WithoutLogicalName_GeneratesAndSavesOne()
        {
            // Arrange
            var parameters = new CreateGameObjectParams { gameObjectName = "MyObjectWithGeneratedName" };
            var command = new CreateGameObjectCommand(parameters.ToJson());

            // Act
            var outcome = command.Execute(_context);
            var createdObject = FindObjectInScene("MyObjectWithGeneratedName");

            // Assert
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.IsNotNull(createdObject);
            Assert.IsNotNull(_context.CurrentSubject, "A logical name should have been generated and set as the subject.");
            Assert.IsTrue(((string)_context.CurrentSubject).StartsWith("MyObjectWithGeneratedName_"));
            Assert.IsTrue(_context.IdentifierMap.ContainsKey((string)_context.CurrentSubject));
        }
    }
}
