using System.Threading.Tasks;
using NUnit.Framework;
using Robust.Shared.ContentPack;
using Robust.Shared.EntitySerialization;
using Robust.Shared.EntitySerialization.Components;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Utility;
using static Robust.UnitTesting.Shared.EntitySerialization.EntitySaveTestComponent;

namespace Robust.UnitTesting.Shared.EntitySerialization;

/// <summary>
/// Test that older file formats can still be loaded.
/// </summary>
[TestFixture]
public sealed partial class BackwardsCompatibilityTest : RobustIntegrationTest
{
    /// <summary>
    /// Check that v7 maps can be loaded. This just re-uses some map files that are generated by other tests, and then
    /// checks that the post-load debug asserts still pass. specifically, it uses the second to last file from
    /// <see cref="AutoIncludeSerializationTest"/> and the initial file from
    /// <see cref="LifestageSerializationTest.TestMixedLifestageSerialization"/>.
    /// </summary>
    /// <remarks>
    /// At the time of writing, v7 is the current version, but this is here for when the version increases in the future.
    /// </remarks>
    [Test]
    public async Task TestLoadV7()
    {
        var server = StartServer();
        await server.WaitIdleAsync();
        var entMan = server.EntMan;
        var mapSys = server.System<SharedMapSystem>();
        var loader = server.System<MapLoaderSystem>();
        var tileMan = server.ResolveDependency<ITileDefinitionManager>();
        var resourceManager = server.ResolveDependency<IResourceManagerInternal>();

        tileMan.Register(new TileDef("space"));
        tileMan.Register(new TileDef("a"));

        void AssertCount(int expected) => Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(expected));

        await server.WaitPost(() => mapSys.CreateMap(out _));
        var mapLoadOpts = MapLoadOptions.Default with
        {
            DeserializationOptions = DeserializationOptions.Default with {LogOrphanedGrids = false}
        };

        // Test the file from AutoIncludeSerializationTest
        {
            var path = new ResPath($"{nameof(MapDataV7)}.yml");
            resourceManager.MountString(path.ToString(), MapDataV7);

            Entity<TransformComponent, EntitySaveTestComponent> grid;
            Entity<TransformComponent, EntitySaveTestComponent> onGrid;
            Entity<TransformComponent, EntitySaveTestComponent> otherMap;
            Entity<TransformComponent, EntitySaveTestComponent> otherEnt;
            Entity<TransformComponent, EntitySaveTestComponent> nullSpace;

            LoadResult? result = default;
            await server.WaitAssertion(() => Assert.That(loader.TryLoadGeneric(path, out result, mapLoadOpts)));

            Assert.That(result!.Version, Is.EqualTo(7));
            Assert.That(result.Grids, Has.Count.EqualTo(1));
            Assert.That(result.Orphans, Is.Empty); // Grid was orphaned, but was adopted after a new map was created
            Assert.That(result.Maps, Has.Count.EqualTo(2));
            Assert.That(result.NullspaceEntities, Has.Count.EqualTo(1));
            Assert.That(entMan.Count<LoadedMapComponent>(), Is.EqualTo(1)); // auto-generated map isn't marked as "loaded"
            AssertCount(5);

            grid = Find(nameof(grid), entMan);
            onGrid = Find(nameof(onGrid), entMan);
            otherMap = Find(nameof(otherMap), entMan);
            otherEnt = Find(nameof(otherEnt), entMan);
            nullSpace = Find(nameof(nullSpace), entMan);

            Assert.That(onGrid.Comp1.ParentUid, Is.EqualTo(grid.Owner));
            Assert.That(otherEnt.Comp1.ParentUid, Is.EqualTo(otherMap.Owner));
            Assert.That(otherMap.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));
            Assert.That(nullSpace.Comp1.ParentUid, Is.EqualTo(EntityUid.Invalid));

            await server.WaitPost(() => entMan.DeleteEntity(otherMap));
            await server.WaitPost(() => entMan.DeleteEntity(grid.Comp1.ParentUid));
            await server.WaitPost(() => entMan.DeleteEntity(nullSpace));
            AssertCount(0);
        }


        // Test the file from LifestageSerializationTest.TestMixedLifestageSerialization
        {
            var pathLifestage = new ResPath($"{nameof(MapDataV7Lifestage)}.yml");
            resourceManager.MountString(pathLifestage.ToString(), MapDataV7Lifestage);

            Entity<TransformComponent, EntitySaveTestComponent> mapA; // preinit Map
            Entity<TransformComponent, EntitySaveTestComponent> mapB; // postinit unpaused Map
            Entity<TransformComponent, EntitySaveTestComponent> entA; // postinit entity on preinit map
            Entity<TransformComponent, EntitySaveTestComponent> entB; // paused entity on postinit unpaused map
            Entity<TransformComponent, EntitySaveTestComponent> entC; // preinit entity on postinit map
            Entity<TransformComponent, EntitySaveTestComponent> nullA; // postinit nullspace entity
            Entity<TransformComponent, EntitySaveTestComponent> nullB; // preinit nullspace entity
            Entity<TransformComponent, EntitySaveTestComponent> nullC; // paused postinit nullspace entity

            Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(0));
            LoadResult? result = default;
            await server.WaitPost(() => Assert.That(loader.TryLoadGeneric(pathLifestage, out result)));
            Assert.That(result!.Version, Is.EqualTo(7));
            Assert.That(entMan.Count<EntitySaveTestComponent>(), Is.EqualTo(8));

            mapA = Find(nameof(mapA), entMan);
            mapB = Find(nameof(mapB), entMan);
            entA = Find(nameof(entA), entMan);
            entB = Find(nameof(entB), entMan);
            entC = Find(nameof(entC), entMan);
            nullA = Find(nameof(nullA), entMan);
            nullB = Find(nameof(nullB), entMan);
            nullC = Find(nameof(nullC), entMan);

            AssertPaused(true, mapA, entB, nullC);
            AssertPaused(false, mapB, entA, entC, nullA, nullB);
            AssertPreInit(true, mapA, entC, nullB);
            AssertPreInit(false, mapB, entA, entB, nullA, nullC);

            void AssertPaused(bool expected, params EntityUid[] uids)
            {
                foreach (var uid in uids)
                {
                    Assert.That(entMan.GetComponent<MetaDataComponent>(uid).EntityPaused, Is.EqualTo(expected));
                }
            }

            void AssertPreInit(bool expected, params EntityUid[] uids)
            {
                foreach (var uid in uids)
                {
                    Assert.That(entMan!.GetComponent<MetaDataComponent>(uid).EntityLifeStage,
                        expected
                            ? Is.LessThan(EntityLifeStage.MapInitialized)
                            : Is.EqualTo(EntityLifeStage.MapInitialized));
                }
            }
        }
    }

    private const string MapDataV7 = @"
meta:
  format: 7
  category: Unknown
  engineVersion: 238.0.0
  forkId: """"
  forkVersion: """"
  time: 12/25/2024 00:40:09
  entityCount: 5
maps:
- 3
grids:
- 1
orphans:
- 1
nullspace:
- 5
tilemap:
  1: space
  0: a
entities:
- proto: """"
  entities:
  - uid: 1
    paused: false
    components:
    - type: MetaData
      name: grid
    - type: Transform
      parent: invalid
    - type: MapGrid
      chunks:
        0,0:
          ind: 0,0
          tiles: AAAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAAAQAAAAAA
          version: 6
    - type: Broadphase
    - type: Physics
    - type: Fixtures
      fixtures: {}
    - type: OccluderTree
    - type: EntitySaveTest
      list: []
      id: grid
  - uid: 2
    mapInit: true
    components:
    - type: MetaData
    - type: Transform
      pos: 0.5,0.5
      parent: 1
    - type: EntitySaveTest
      list: []
      entity: 3
      id: onGrid
  - uid: 3
    mapInit: true
    components:
    - type: MetaData
      name: Map Entity
    - type: Transform
    - type: Map
      mapInitialized: True
    - type: PhysicsMap
    - type: GridTree
    - type: MovedGrids
    - type: Broadphase
    - type: OccluderTree
    - type: EntitySaveTest
      list: []
      id: otherMap
  - uid: 4
    mapInit: true
    components:
    - type: MetaData
    - type: Transform
      parent: 3
    - type: EntitySaveTest
      list: []
      entity: 5
      id: otherEnt
  - uid: 5
    mapInit: true
    components:
    - type: MetaData
    - type: Transform
    - type: EntitySaveTest
      list: []
      id: nullSpace
";

    private const string MapDataV7Lifestage = @"
meta:
  format: 7
  category: Unknown
  engineVersion: 238.0.0
  forkId: """"
  forkVersion: """"
  time: 12/25/2024 00:50:59
  entityCount: 8
maps:
- 1
- 3
grids: []
orphans: []
nullspace:
- 6
- 7
- 8
tilemap: {}
entities:
- proto: """"
  entities:
  - uid: 1
    components:
    - type: MetaData
      name: Map Entity
    - type: Transform
    - type: Map
      mapPaused: True
    - type: PhysicsMap
    - type: GridTree
    - type: MovedGrids
    - type: Broadphase
    - type: OccluderTree
    - type: EntitySaveTest
      list: []
      id: mapA
  - uid: 2
    mapInit: true
    components:
    - type: MetaData
    - type: Transform
      parent: 1
    - type: EntitySaveTest
      list: []
      id: entA
  - uid: 3
    mapInit: true
    components:
    - type: MetaData
      name: Map Entity
    - type: Transform
    - type: Map
      mapInitialized: True
    - type: PhysicsMap
    - type: GridTree
    - type: MovedGrids
    - type: Broadphase
    - type: OccluderTree
    - type: EntitySaveTest
      list: []
      id: mapB
  - uid: 4
    mapInit: true
    paused: true
    components:
    - type: MetaData
    - type: Transform
      parent: 3
    - type: EntitySaveTest
      list: []
      id: entB
  - uid: 5
    paused: false
    components:
    - type: MetaData
    - type: Transform
      parent: 3
    - type: EntitySaveTest
      list: []
      id: entC
  - uid: 6
    mapInit: true
    components:
    - type: MetaData
    - type: Transform
    - type: EntitySaveTest
      list: []
      id: nullA
  - uid: 7
    paused: false
    components:
    - type: MetaData
    - type: Transform
    - type: EntitySaveTest
      list: []
      id: nullB
  - uid: 8
    mapInit: true
    paused: true
    components:
    - type: MetaData
    - type: Transform
    - type: EntitySaveTest
      list: []
      id: nullC
";
}
