using Ghost.Core;
using Ghost.Editor.Core;
using Ghost.Editor.Core.Assets;
using Ghost.Editor.Core.Services;
using Ghost.Engine;
using Ghost.Engine.Components;
using Ghost.Engine.Core;
using Ghost.Engine.Streaming;
using Ghost.Entities;
using Misaki.HighPerformance.LowLevel.Buffer;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;

namespace Ghost.UnitTest;

[TestClass]
[DoNotParallelize]
public class SceneSerializationTests
{
    private sealed class EmptyServiceProvider : IServiceProvider
    {
        public object? GetService(Type serviceType)
        {
            return null;
        }
    }

    private string _projectRoot = null!;
    private string _previousCurrentDirectory = null!;
    private EditorWorldService _worldService = null!;
    private SceneSerializationService _serializationService = null!;

    [TestInitialize]
    public void Setup()
    {
        _previousCurrentDirectory = Environment.CurrentDirectory;
        _projectRoot = Path.Combine(Path.GetTempPath(), "GhostEngineTests", Guid.NewGuid().ToString());
        Directory.CreateDirectory(_projectRoot);
        EditorApplication.Initialize(new EmptyServiceProvider(), _projectRoot, "SceneTest");

        _worldService = new EditorWorldService();
        var syncService = new SceneGraphSyncService(_worldService);
        _serializationService = new SceneSerializationService(_worldService, null!, syncService);
    }

    [TestCleanup]
    public void Cleanup()
    {
        _worldService.Dispose();

        Environment.CurrentDirectory = _previousCurrentDirectory;

        if (Directory.Exists(_projectRoot))
        {
            try
            {
                Directory.Delete(_projectRoot, true);
            }
            catch (IOException)
            {
                Thread.Sleep(100);
                if (Directory.Exists(_projectRoot))
                {
                    Directory.Delete(_projectRoot, true);
                }
            }
        }
    }

    private Entity CreateSceneEntity(Scene scene)
    {
        var world = _worldService.EditorWorld;
        var entity = world.EntityManager.CreateEntity();
        world.EntityManager.AddSharedComponent(entity, new SceneID { value = scene.ID });
        return entity;
    }

    private Entity CreateEntityWithHierarchy(Scene scene, Entity parent)
    {
        var world = _worldService.EditorWorld;
        var entity = world.EntityManager.CreateEntity();
        world.EntityManager.AddSharedComponent(entity, new SceneID { value = scene.ID });
        world.EntityManager.AddComponent(entity, Hierarchy.Root);
        world.EntityManager.AddComponent(entity, new LocalToWorld());

        if (parent.IsValid)
        {
            HierarchyUtility.SetParent(world, entity, parent);
        }

        return entity;
    }

    [TestMethod]
    public async Task SaveAndLoad_RoundTrip_PreservesEntityCount()
    {
        var scene = SceneManager.CreateScene();
        CreateSceneEntity(scene);
        CreateEntityWithHierarchy(scene, Entity.Invalid);
        CreateEntityWithHierarchy(scene, Entity.Invalid);

        var filePath = Path.Combine(_projectRoot, "TestScene.gscene");
        _serializationService.SaveSceneFromEditorWorld(filePath, scene);

        Assert.IsTrue(File.Exists(filePath));

        var json = await File.ReadAllTextAsync(filePath, TestContext.CancellationToken);
        Assert.IsGreaterThan(10, json.Length, $"JSON too short: {json}");

        var data = await SceneSerializationService.DeserializeSceneFileAsync(filePath, TestContext.CancellationToken);
        Assert.IsNotNull(data);
        Assert.HasCount(3, data.Entities, $"JSON content: {json}");

        // Verify saved JSON is valid
        Assert.HasCount(3, data.Entities, $"data contained {data.Entities.Count} entities");
        foreach (var ent in data.Entities)
        {
            Assert.IsGreaterThanOrEqualTo(0, ent.Components.Count, "Entity has no components"); // Can be 0 because we might have entities without components, but should not be negative
        }

        _serializationService.LoadSceneIntoEditorWorld(data);
        _worldService.FlushCommands();
        _worldService.FirePendingEvents();

        var world = _worldService.EditorWorld;

        var queryID = new QueryBuilder().WithAll<SceneID>().Build(world);
        ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
        var loadedCount = 0;
        foreach (var chunk in query.GetChunkIterator())
        {
            loadedCount += chunk.EntityCount;
        }
        Assert.AreEqual(3, loadedCount, $"Expected 3 entities but found {loadedCount}");
    }

    [TestMethod]
    public async Task SaveAndLoad_HierarchyRelations_Preserved()
    {
        var scene = SceneManager.CreateScene();
        CreateSceneEntity(scene);

        var parent = CreateEntityWithHierarchy(scene, Entity.Invalid);
        var child = CreateEntityWithHierarchy(scene, parent);

        var world = _worldService.EditorWorld;

        var filePath = Path.Combine(_projectRoot, "HierarchyScene.gscene");
        _serializationService.SaveSceneFromEditorWorld(filePath, scene);

        var data = await SceneSerializationService.DeserializeSceneFileAsync(filePath, TestContext.CancellationToken);
        Assert.IsNotNull(data);
        Assert.HasCount(3, data.Entities);

        _serializationService.LoadSceneIntoEditorWorld(data);
        _worldService.FlushCommands();
        _worldService.FirePendingEvents();

        var queryID = new QueryBuilder().WithAll<SceneID, Hierarchy>().Build(world);
        ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);

        var entities = new List<Entity>();
        foreach (var chunk in query.GetChunkIterator())
        {
            var chunkEntities = chunk.GetEntities();
            entities.AddRange(chunkEntities.ToArray());
        }

        var children = entities.Where(e =>
        {
            ref var h = ref world.EntityManager.GetComponent<Hierarchy>(e);
            return h.parent.IsValid;
        }).ToList();

        Assert.HasCount(1, children);
    }

    [TestMethod]
    public async Task JsonFormat_EntityFieldsBecomeIntIndices()
    {
        var scene = SceneManager.CreateScene();
        CreateSceneEntity(scene);
        var parent = CreateEntityWithHierarchy(scene, Entity.Invalid);
        var child = CreateEntityWithHierarchy(scene, parent);

        var filePath = Path.Combine(_projectRoot, "JsonFormat.gscene");
        _serializationService.SaveSceneFromEditorWorld(filePath, scene);

        var json = await File.ReadAllTextAsync(filePath, TestContext.CancellationToken);
        using var doc = JsonDocument.Parse(json);

        var root = doc.RootElement;
        Assert.AreEqual(1, root.GetProperty("formatVersion").GetInt32());

        var entities = root.GetProperty("entities");
        Assert.AreEqual(3, entities.GetArrayLength());

        var hasParentChild = false;
        foreach (var entityElement in entities.EnumerateArray())
        {
            var components = entityElement.GetProperty("components");
            if (components.TryGetProperty("Ghost.Engine.Components.Hierarchy", out var hierarchyElement))
            {
                var jsonStr = hierarchyElement.GetRawText();
                var hierarchyDoc = JsonDocument.Parse(jsonStr);

                if (hierarchyDoc.RootElement.TryGetProperty("parent", out var parentProp))
                {
                    var parentValue = parentProp.GetInt32();
                    if (parentValue != -1)
                    {
                        hasParentChild = true;
                    }
                }

                if (hierarchyDoc.RootElement.TryGetProperty("firstChild", out var firstChildProp))
                {
                    var firstChildValue = firstChildProp.GetInt32();
                    if (firstChildValue != -1)
                    {
                        hasParentChild = true;
                    }
                }
            }
        }

        Assert.IsTrue(hasParentChild, "Expected at least one parent-child relationship in the JSON output.");
    }

    [TestMethod]
    public async Task ImportAsync_ProducesValidBinary()
    {
        var scene = SceneManager.CreateScene();
        CreateSceneEntity(scene);
        CreateEntityWithHierarchy(scene, Entity.Invalid);

        var sourcePath = Path.Combine(_projectRoot, "ImportScene.gscene");
        _serializationService.SaveSceneFromEditorWorld(sourcePath, scene);

        var targetPath = ImportCoordinator.GetImportedAssetPath(Guid.NewGuid());
        var handler = new SceneAssetHandler();
        var result = await handler.ImportAsync(sourcePath, targetPath, Guid.NewGuid(), null, TestContext.CancellationToken);

        Assert.IsTrue(result.IsSuccess, result.Message);
        Assert.IsTrue(File.Exists(targetPath));

        var binary = await File.ReadAllBytesAsync(targetPath, TestContext.CancellationToken);
        Assert.IsNotEmpty(binary);

        var magic = Encoding.UTF8.GetString(binary, 0, 4);
        Assert.AreEqual("GSCN", magic);

        var version = MemoryMarshal.Read<int>(binary.AsSpan(4));
        Assert.AreEqual(1, version);

        var entityCount = MemoryMarshal.Read<int>(binary.AsSpan(8));
        Assert.AreEqual(2, entityCount);
    }

    [TestMethod]
    public async Task SceneLoadingType_Single_ReplacesEntities()
    {
        var scene = SceneManager.CreateScene();
        CreateSceneEntity(scene);
        CreateEntityWithHierarchy(scene, Entity.Invalid);

        var scene2 = SceneManager.CreateScene();
        CreateSceneEntity(scene2);
        CreateEntityWithHierarchy(scene2, Entity.Invalid);

        var world = _worldService.EditorWorld;
        var queryID = new QueryBuilder().WithAll<SceneID>().Build(world);
        ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
        var initialCount = 0;
        foreach (var chunk in query.GetChunkIterator())
        {
            initialCount += chunk.EntityCount;
        }

        Assert.AreEqual(4, initialCount, "Expected 4 entities.");

        var filePath = Path.Combine(_projectRoot, "SingleLoad.gscene");
        _serializationService.SaveSceneFromEditorWorld(filePath, scene);

        var data = await SceneSerializationService.DeserializeSceneFileAsync(filePath, TestContext.CancellationToken);
        Assert.IsNotNull(data);

        _serializationService.LoadSceneIntoEditorWorld(data, SceneLoadingType.Single);
        _worldService.FlushCommands();
        _worldService.FirePendingEvents();

        var afterCount = 0;
        query = ref world.ComponentManager.GetEntityQueryReference(queryID);
        foreach (var chunk in query.GetChunkIterator())
        {
            afterCount += chunk.EntityCount;
        }

        Assert.AreEqual(2, afterCount, "Expected exactly 2 entities after Single load (previous entities cleared).");
    }

    [TestMethod]
    public void Save_EmptyScene_ProducesEmptyFile()
    {
        var scene = SceneManager.CreateScene();

        var filePath = Path.Combine(_projectRoot, "EmptyScene.gscene");
        _serializationService.SaveSceneFromEditorWorld(filePath, scene);

        Assert.IsTrue(File.Exists(filePath));
    }

    [TestMethod]
    public async Task Load_UnknownComponent_SkipsGracefully()
    {
        var json = @"
		{
			""formatVersion"": 1,
			""entities"": [
				{
					""components"": {
						""Some.Unknown.Component"": { ""foo"": 1 },
						""Ghost.Engine.Components.Hierarchy"": { ""parent"": -1, ""firstChild"": -1, ""nextSibling"": -1 }
					}
				}
			]
		}";

        var filePath = Path.Combine(_projectRoot, "UnknownComp.gscene");
        await File.WriteAllTextAsync(filePath, json, TestContext.CancellationToken);

        var data = await SceneSerializationService.DeserializeSceneFileAsync(filePath, TestContext.CancellationToken);
        Assert.IsNotNull(data);

        _serializationService.LoadSceneIntoEditorWorld(data);
        _worldService.FlushCommands();
        _worldService.FirePendingEvents();

        var world = _worldService.EditorWorld;
        var queryID = new QueryBuilder().WithAll<Hierarchy>().Build(world);
        ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
        var entityFound = false;
        foreach (var chunk in query.GetChunkIterator())
        {
            if (chunk.EntityCount > 0)
            {
                entityFound = true;
                break;
            }
        }

        Assert.IsTrue(entityFound, "Expected entity with Hierarchy component to be loaded.");
    }

    [TestMethod]
    public async Task Load_InvalidVersion_StillLoads()
    {
        var json = @"
		{
			""formatVersion"": 999,
			""entities"": [
				{
					""components"": {
						""Ghost.Engine.Components.Hierarchy"": { ""parent"": -1, ""firstChild"": -1, ""nextSibling"": -1 }
					}
				}
			]
		}";

        var filePath = Path.Combine(_projectRoot, "FutureVersion.gscene");
        await File.WriteAllTextAsync(filePath, json, TestContext.CancellationToken);

        var data = await SceneSerializationService.DeserializeSceneFileAsync(filePath, TestContext.CancellationToken);
        Assert.IsNotNull(data);
        Assert.AreEqual(999u, data.FormatVersion);

        _serializationService.LoadSceneIntoEditorWorld(data);
        _worldService.FlushCommands();
        _worldService.FirePendingEvents();
    }

    [TestMethod]
    public async Task BinaryFormat_RoundTrip_ProducesLoadableData()
    {
        var scene = SceneManager.CreateScene();
        CreateSceneEntity(scene);
        var parent = CreateEntityWithHierarchy(scene, Entity.Invalid);
        CreateEntityWithHierarchy(scene, parent);

        var jsonPath = Path.Combine(_projectRoot, "BinaryRoundTrip.gscene");
        _serializationService.SaveSceneFromEditorWorld(jsonPath, scene);

        var data = await SceneSerializationService.DeserializeSceneFileAsync(jsonPath, TestContext.CancellationToken);
        Assert.IsNotNull(data);

        using var stream = new MemoryStream();
        SceneSerializationService.SerializeToBinary(data, stream);
        var binary = stream.ToArray();

        var world = World.Create(entityCapacity: 64);
        try
        {
            Result<LoadedSceneData> sceneDataResult;
            unsafe
            {
                fixed (byte* pBinary = binary)
                {
                    sceneDataResult = SceneManager.ParseSceneData(*(SceneContentHeader*)pBinary, pBinary + sizeof(SceneContentHeader), (nuint)(binary.Length + sizeof(SceneContentHeader)), AllocationHandle.Persistent);
                }
            }

            Assert.IsTrue(sceneDataResult.IsSuccess, sceneDataResult.Message);
            Assert.AreEqual(3, sceneDataResult.Value.entities.Count);

            using var sceneData = sceneDataResult.Value;
            SceneManager.MaterializeScene(world, in sceneData, scene, 0, sceneData.entities.Count);

            var queryID = new QueryBuilder().WithAll<Hierarchy>().Build(world);
            ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
            var entityCount = 0;
            foreach (var chunk in query.GetChunkIterator())
            {
                entityCount += chunk.EntityCount;
            }

            Assert.AreEqual(2, entityCount, "Expected 2 entities with Hierarchy component.");
        }
        finally
        {
            world.Dispose();
        }
    }

    [TestMethod]
    public async Task SaveAndLoad_LocalToWorld_Preserved()
    {
        var scene = SceneManager.CreateScene();
        CreateSceneEntity(scene);
        var entity = CreateEntityWithHierarchy(scene, Entity.Invalid);

        var world = _worldService.EditorWorld;
        var testMatrix = new Misaki.HighPerformance.Mathematics.float4x4(
            1, 0, 0, 10,
            0, 1, 0, 20,
            0, 0, 1, 30,
            0, 0, 0, 1
        );
        world.EntityManager.SetComponent(entity, new LocalToWorld { matrix = testMatrix });

        var filePath = Path.Combine(_projectRoot, "TransformScene.gscene");
        _serializationService.SaveSceneFromEditorWorld(filePath, scene);

        var data = await SceneSerializationService.DeserializeSceneFileAsync(filePath, TestContext.CancellationToken);
        Assert.IsNotNull(data);

        _serializationService.LoadSceneIntoEditorWorld(data);
        _worldService.FlushCommands();
        _worldService.FirePendingEvents();

        var queryID = new QueryBuilder().WithAll<LocalToWorld>().Build(world);
        ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);
        foreach (var chunk in query.GetChunkIterator())
        {
            var ltws = chunk.GetComponentData<LocalToWorld>();
            var found = false;
            foreach (ref readonly var ltw in ltws)
            {
                if (ltw.matrix.c3.x == 10 && ltw.matrix.c3.y == 20 && ltw.matrix.c3.z == 30)
                {
                    found = true;
                    break;
                }
            }

            if (found)
            {
                return;
            }
        }

        Assert.Fail("LocalToWorld component with expected transform was not found after round-trip.");
    }

    public TestContext TestContext
    {
        get; set;
    } = null!;
}
