using NUnit.Framework;
using UnityEngine;
using LLM.Editor.Commands;
using LLM.Editor.Data;
using LLM.Editor.Core;
using Newtonsoft.Json.Linq;

namespace LLM.Editor.Tests.Unit
{
    public class TestComponent : MonoBehaviour
    {
        public string stringValue;
        public int intValue;
        public float floatValue;
        public bool boolValue;
        public Vector3 vector3Value;
    }

    [TestFixture]
    public class SetComponentValueCommandTests
    {
        private GameObject _testObject;
        private TestComponent _testComponent;
        private CommandContext _context;
        private string _logicalName;

        [SetUp]
        public void SetUp()
        {
            _testObject = new GameObject("TestObject");
            _testComponent = _testObject.AddComponent<TestComponent>();
            _testObject.AddComponent<Rigidbody>();
            _context = new CommandContext();
            
            _logicalName = "test_object_1";
            var pid = new PersistentIdentifier
            {
                logicalName = _logicalName,
                instanceID = _testObject.GetInstanceID(),
                path = CommandUtility.GetGameObjectPath(_testObject)
            };
            _context.IdentifierMap[_logicalName] = pid;
        }

        [TearDown]
        public void TearDown()
        {
            if (_testObject != null)
            {
                Object.DestroyImmediate(_testObject);
            }
        }
        
        private SetComponentValueCommand CreateCommand(string componentName, JArray members)
        {
            var json = new JObject
            {
                ["subjectIdentifier"] = _logicalName,
                ["componentName"] = componentName,
                ["members"] = members
            }.ToString();
            return new SetComponentValueCommand(json);
        }

        [Test]
        public void Execute_SetsStringValue_Successfully()
        {
            var members = new JArray { new JObject { ["memberName"] = "stringValue", ["value"] = "Hello World" } };
            var command = CreateCommand("LLM.Editor.Tests.Unit.TestComponent", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.AreEqual("Hello World", _testComponent.stringValue);
        }

        [Test]
        public void Execute_SetsIntValue_Successfully()
        {
            var members = new JArray { new JObject { ["memberName"] = "intValue", ["value"] = 42 } };
            var command = CreateCommand("LLM.Editor.Tests.Unit.TestComponent", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.AreEqual(42, _testComponent.intValue);
        }

        [Test]
        public void Execute_SetsFloatValue_Successfully()
        {
            var members = new JArray { new JObject { ["memberName"] = "floatValue", ["value"] = 3.14f } };
            var command = CreateCommand("LLM.Editor.Tests.Unit.TestComponent", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.AreEqual(3.14f, _testComponent.floatValue);
        }

        [Test]
        public void Execute_SetsBoolValue_Successfully()
        {
            var members = new JArray { new JObject { ["memberName"] = "boolValue", ["value"] = true } };
            var command = CreateCommand("LLM.Editor.Tests.Unit.TestComponent", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.IsTrue(_testComponent.boolValue);
        }

        [Test]
        public void Execute_SetsVector3Value_Successfully()
        {
            var vec3Json = new JObject { ["x"] = 1, ["y"] = 2, ["z"] = 3 };
            var members = new JArray { new JObject { ["memberName"] = "vector3Value", ["value"] = vec3Json } };
            var command = CreateCommand("LLM.Editor.Tests.Unit.TestComponent", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.AreEqual(new Vector3(1, 2, 3), _testComponent.vector3Value);
        }

        [Test]
        public void Execute_WithInvalidMemberName_ReturnsError()
        {
            var members = new JArray { new JObject { ["memberName"] = "invalidMember", ["value"] = "someValue" } };
            var command = CreateCommand("LLM.Editor.Tests.Unit.TestComponent", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Error, outcome);
            Assert.IsNotNull(_context.ErrorMessage);
            Assert.IsTrue(_context.ErrorMessage.Contains("Could not find a writable serialized field or public property"));
        }

        [Test]
        public void Execute_SetTransformLocalPositionValue_Successfully()
        {
            var members = new JArray { new JObject { ["memberName"] = "position", ["value"] = new JObject { ["x"] = 1, ["y"] = 2, ["z"] = 3 } } };
            var command = CreateCommand("UnityEngine.Transform", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.AreEqual(new Vector3(1, 2, 3), _testComponent.transform.position);
        }
        
        [Test]
        public void Execute_SetTransformLocalRotationValue_Successfully()
        {
            var members = new JArray { new JObject { ["memberName"] = "eulerAngles", ["value"] = new JObject { ["x"] = 1, ["y"] = 2, ["z"] = 3 } } };
            var command = CreateCommand("UnityEngine.Transform", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.IsTrue(IsSame(_testComponent.transform.eulerAngles, new Vector3(1, 2, 3)));
            bool IsSame(Vector3 a, Vector3 b) => Mathf.Approximately(a.x, b.x) && Mathf.Approximately(a.y, b.y) && Mathf.Approximately(a.z, b.z);
        }
        
        [Test]
        public void Execute_SetTransformLocalScaleValue_Successfully()
        {
            var members = new JArray { new JObject { ["memberName"] = "localScale", ["value"] = new JObject { ["x"] = 1, ["y"] = 2, ["z"] = 3 } } };
            var command = CreateCommand("UnityEngine.Transform", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.AreEqual(new Vector3(1, 2, 3), _testComponent.transform.localScale);
        }
        
        [Test]
        public void Execute_DisableBoxCollider_Successfully()
        {
            if (!_testComponent.TryGetComponent(out BoxCollider boxCollider))
            {
                boxCollider = _testObject.AddComponent<BoxCollider>();
            }
            var members = new JArray { new JObject { ["memberName"] = "enabled", ["value"] = false } };
            var command = CreateCommand("UnityEngine.BoxCollider", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.IsFalse(boxCollider.enabled);
        }

        [Test]
        public void Execute_SetsMultipleValues_Successfully()
        {
            var members = new JArray
            {
                new JObject { ["memberName"] = "stringValue", ["value"] = "Multi-set" },
                new JObject { ["memberName"] = "intValue", ["value"] = 123 },
                new JObject { ["memberName"] = "boolValue", ["value"] = true }
            };
            var command = CreateCommand("LLM.Editor.Tests.Unit.TestComponent", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.AreEqual("Multi-set", _testComponent.stringValue);
            Assert.AreEqual(123, _testComponent.intValue);
            Assert.IsTrue(_testComponent.boolValue);
        }

        [Test]
        public void Execute_SetsMultipleTransformValues_Successfully()
        {
            var members = new JArray
            {
                new JObject
                {
                    ["memberName"] = "position",
                    ["value"] = new JObject { ["x"] = 5, ["y"] = 6, ["z"] = 7 }
                },
                new JObject
                {
                    ["memberName"] = "localScale",
                    ["value"] = new JObject { ["x"] = 2, ["y"] = 2, ["z"] = 2 }
                }
            };
            var command = CreateCommand("UnityEngine.Transform", members);
            var outcome = command.Execute(_context);
            Assert.AreEqual(CommandOutcome.Success, outcome);
            Assert.AreEqual(new Vector3(5, 6, 7), _testObject.transform.position);
            Assert.AreEqual(new Vector3(2, 2, 2), _testObject.transform.localScale);
        }
    }
}
