using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Shared;
using System.Linq;

namespace ZB.MOM.WW.CBDD.Tests;

/// <summary>
/// Tests for Source Generator enhancements:
/// 1. Property inheritance from base classes (including Id)
/// 2. Exclusion of computed getter-only properties
/// 3. Recognition of advanced collection types (HashSet, ISet, LinkedList, etc.)
/// </summary>
public class SourceGeneratorFeaturesTests : IDisposable
{
    private readonly string _dbPath;
    private readonly string _walPath;
    private readonly Shared.TestDbContext _db;

    /// <summary>
    /// Initializes a new instance of the <see cref="SourceGeneratorFeaturesTests"/> class.
    /// </summary>
    public SourceGeneratorFeaturesTests()
    {
        _dbPath = Path.Combine(Path.GetTempPath(), $"test_sg_features_{Guid.NewGuid()}.db");
        _walPath = Path.Combine(Path.GetTempPath(), $"test_sg_features_{Guid.NewGuid()}.wal");

        _db = new Shared.TestDbContext(_dbPath);
    }

    #region Inheritance Tests

    /// <summary>
    /// Tests derived entity inherits id from base class.
    /// </summary>
    [Fact]
    public void DerivedEntity_InheritsId_FromBaseClass()
    {
        // Arrange
        var entity = new DerivedEntity
        {
            Name = "Test Entity",
            Description = "Testing inheritance",
            CreatedAt = DateTime.UtcNow
        };

        // Act
        var id = _db.DerivedEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.DerivedEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.Id.ShouldBe(id); // Id from base class should work
        retrieved.Name.ShouldBe("Test Entity");
        retrieved.Description.ShouldBe("Testing inheritance");
        retrieved.CreatedAt.Date.ShouldBe(entity.CreatedAt.Date); // Compare just date part
    }

    /// <summary>
    /// Tests derived entity update works with inherited id.
    /// </summary>
    [Fact]
    public void DerivedEntity_Update_WorksWithInheritedId()
    {
        // Arrange
        var entity = new DerivedEntity
        {
            Name = "Original",
            Description = "Original Description",
            CreatedAt = DateTime.UtcNow
        };
        var id = _db.DerivedEntities.Insert(entity);
        _db.SaveChanges();

        // Act
        var retrieved = _db.DerivedEntities.FindById(id);
        retrieved.ShouldNotBeNull();
        retrieved.Name = "Updated";
        retrieved.Description = "Updated Description";
        _db.DerivedEntities.Update(retrieved);
        _db.SaveChanges();

        var updated = _db.DerivedEntities.FindById(id);

        // Assert
        updated.ShouldNotBeNull();
        updated.Id.ShouldBe(id);
        updated.Name.ShouldBe("Updated");
        updated.Description.ShouldBe("Updated Description");
    }

    /// <summary>
    /// Tests derived entity query works with inherited properties.
    /// </summary>
    [Fact]
    public void DerivedEntity_Query_WorksWithInheritedProperties()
    {
        // Arrange
        var now = DateTime.UtcNow;
        var entity1 = new DerivedEntity { Name = "Entity1", CreatedAt = now.AddDays(-2) };
        var entity2 = new DerivedEntity { Name = "Entity2", CreatedAt = now.AddDays(-1) };
        var entity3 = new DerivedEntity { Name = "Entity3", CreatedAt = now };

        _db.DerivedEntities.Insert(entity1);
        _db.DerivedEntities.Insert(entity2);
        _db.DerivedEntities.Insert(entity3);
        _db.SaveChanges();

        // Act - Query using inherited property
        var recent = _db.DerivedEntities.Find(e => e.CreatedAt >= now.AddDays(-1.5)).ToList();

        // Assert
        recent.Count.ShouldBe(2);
        recent.ShouldContain(e => e.Name == "Entity2");
        recent.ShouldContain(e => e.Name == "Entity3");
    }

    #endregion

    #region Computed Properties Tests

    /// <summary>
    /// Tests computed properties are not serialized.
    /// </summary>
    [Fact]
    public void ComputedProperties_AreNotSerialized()
    {
        // Arrange
        var entity = new EntityWithComputedProperties
        {
            FirstName = "John",
            LastName = "Doe",
            BirthYear = 1990
        };

        // Act
        var id = _db.ComputedPropertyEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.ComputedPropertyEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.FirstName.ShouldBe("John");
        retrieved.LastName.ShouldBe("Doe");
        retrieved.BirthYear.ShouldBe(1990);

        // Computed properties should still work after deserialization
        retrieved.FullName.ShouldBe("John Doe");
        (retrieved.Age >= 34).ShouldBeTrue(); // Born in 1990, so at least 34 in 2024+
        retrieved.DisplayInfo.ShouldContain("John Doe");
    }

    /// <summary>
    /// Tests computed properties update does not break.
    /// </summary>
    [Fact]
    public void ComputedProperties_UpdateDoesNotBreak()
    {
        // Arrange
        var entity = new EntityWithComputedProperties
        {
            FirstName = "Jane",
            LastName = "Smith",
            BirthYear = 1985
        };
        var id = _db.ComputedPropertyEntities.Insert(entity);
        _db.SaveChanges();

        // Act - Update stored properties
        var retrieved = _db.ComputedPropertyEntities.FindById(id);
        retrieved.ShouldNotBeNull();
        retrieved.FirstName = "Janet";
        retrieved.BirthYear = 1986;
        _db.ComputedPropertyEntities.Update(retrieved);
        _db.SaveChanges();

        var updated = _db.ComputedPropertyEntities.FindById(id);

        // Assert
        updated.ShouldNotBeNull();
        updated.FirstName.ShouldBe("Janet");
        updated.LastName.ShouldBe("Smith");
        updated.BirthYear.ShouldBe(1986);
        updated.FullName.ShouldBe("Janet Smith"); // Computed property reflects new data
    }

    #endregion

    #region Advanced Collections Tests

    /// <summary>
    /// Tests hash set serializes and deserializes.
    /// </summary>
    [Fact]
    public void HashSet_SerializesAndDeserializes()
    {
        // Arrange
        var entity = new EntityWithAdvancedCollections
        {
            Name = "Test HashSet"
        };
        entity.Tags.Add("tag1");
        entity.Tags.Add("tag2");
        entity.Tags.Add("tag3");

        // Act
        var id = _db.AdvancedCollectionEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.AdvancedCollectionEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.Tags.ShouldNotBeNull();
        retrieved.Tags.ShouldBeOfType<HashSet<string>>();
        retrieved.Tags.Count.ShouldBe(3);
        retrieved.Tags.ShouldContain("tag1");
        retrieved.Tags.ShouldContain("tag2");
        retrieved.Tags.ShouldContain("tag3");
    }

    /// <summary>
    /// Tests iset serializes and deserializes.
    /// </summary>
    [Fact]
    public void ISet_SerializesAndDeserializes()
    {
        // Arrange
        var entity = new EntityWithAdvancedCollections
        {
            Name = "Test ISet"
        };
        entity.Numbers.Add(10);
        entity.Numbers.Add(20);
        entity.Numbers.Add(30);
        entity.Numbers.Add(10); // Duplicate - should be ignored by set

        // Act
        var id = _db.AdvancedCollectionEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.AdvancedCollectionEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.Numbers.ShouldNotBeNull();
        retrieved.Numbers.ShouldBeAssignableTo<ISet<int>>();
        retrieved.Numbers.Count.ShouldBe(3); // Only unique values
        retrieved.Numbers.ShouldContain(10);
        retrieved.Numbers.ShouldContain(20);
        retrieved.Numbers.ShouldContain(30);
    }

    /// <summary>
    /// Tests linked list serializes and deserializes.
    /// </summary>
    [Fact]
    public void LinkedList_SerializesAndDeserializes()
    {
        // Arrange
        var entity = new EntityWithAdvancedCollections
        {
            Name = "Test LinkedList"
        };
        entity.History.AddLast("first");
        entity.History.AddLast("second");
        entity.History.AddLast("third");

        // Act
        var id = _db.AdvancedCollectionEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.AdvancedCollectionEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.History.ShouldNotBeNull();
        // LinkedList may be deserialized as List, then need conversion
        var historyList = retrieved.History.ToList();
        historyList.Count.ShouldBe(3);
        historyList[0].ShouldBe("first");
        historyList[1].ShouldBe("second");
        historyList[2].ShouldBe("third");
    }

    /// <summary>
    /// Tests queue serializes and deserializes.
    /// </summary>
    [Fact]
    public void Queue_SerializesAndDeserializes()
    {
        // Arrange
        var entity = new EntityWithAdvancedCollections
        {
            Name = "Test Queue"
        };
        entity.PendingItems.Enqueue("item1");
        entity.PendingItems.Enqueue("item2");
        entity.PendingItems.Enqueue("item3");

        // Act
        var id = _db.AdvancedCollectionEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.AdvancedCollectionEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.PendingItems.ShouldNotBeNull();
        retrieved.PendingItems.Count.ShouldBe(3);
        var items = retrieved.PendingItems.ToList();
        items.ShouldContain("item1");
        items.ShouldContain("item2");
        items.ShouldContain("item3");
    }

    /// <summary>
    /// Tests stack serializes and deserializes.
    /// </summary>
    [Fact]
    public void Stack_SerializesAndDeserializes()
    {
        // Arrange
        var entity = new EntityWithAdvancedCollections
        {
            Name = "Test Stack"
        };
        entity.UndoStack.Push("action1");
        entity.UndoStack.Push("action2");
        entity.UndoStack.Push("action3");

        // Act
        var id = _db.AdvancedCollectionEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.AdvancedCollectionEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.UndoStack.ShouldNotBeNull();
        retrieved.UndoStack.Count.ShouldBe(3);
        var items = retrieved.UndoStack.ToList();
        items.ShouldContain("action1");
        items.ShouldContain("action2");
        items.ShouldContain("action3");
    }

    /// <summary>
    /// Tests hash set with nested objects serializes and deserializes.
    /// </summary>
    [Fact]
    public void HashSet_WithNestedObjects_SerializesAndDeserializes()
    {
        // Arrange
        var entity = new EntityWithAdvancedCollections
        {
            Name = "Test Nested HashSet"
        };
        entity.Addresses.Add(new Address { Street = "123 Main St", City = new City { Name = "NYC", ZipCode = "10001" } });
        entity.Addresses.Add(new Address { Street = "456 Oak Ave", City = new City { Name = "LA", ZipCode = "90001" } });

        // Act
        var id = _db.AdvancedCollectionEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.AdvancedCollectionEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.Addresses.ShouldNotBeNull();
        retrieved.Addresses.ShouldBeOfType<HashSet<Address>>();
        retrieved.Addresses.Count.ShouldBe(2);

        var addressList = retrieved.Addresses.ToList();
        addressList.ShouldContain(a => a.Street == "123 Main St" && a.City.Name == "NYC");
        addressList.ShouldContain(a => a.Street == "456 Oak Ave" && a.City.Name == "LA");
    }

    /// <summary>
    /// Tests iset with nested objects serializes and deserializes.
    /// </summary>
    [Fact]
    public void ISet_WithNestedObjects_SerializesAndDeserializes()
    {
        // Arrange
        var entity = new EntityWithAdvancedCollections
        {
            Name = "Test Nested ISet"
        };
        entity.FavoriteCities.Add(new City { Name = "Paris", ZipCode = "75001" });
        entity.FavoriteCities.Add(new City { Name = "Tokyo", ZipCode = "100-0001" });
        entity.FavoriteCities.Add(new City { Name = "London", ZipCode = "SW1A" });

        // Act
        var id = _db.AdvancedCollectionEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.AdvancedCollectionEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.FavoriteCities.ShouldNotBeNull();
        retrieved.FavoriteCities.ShouldBeAssignableTo<ISet<City>>();
        retrieved.FavoriteCities.Count.ShouldBe(3);

        var cityNames = retrieved.FavoriteCities.Select(c => c.Name).ToList();
        cityNames.ShouldContain("Paris");
        cityNames.ShouldContain("Tokyo");
        cityNames.ShouldContain("London");
    }

    /// <summary>
    /// Tests advanced collections all types in single entity.
    /// </summary>
    [Fact]
    public void AdvancedCollections_AllTypesInSingleEntity()
    {
        // Arrange - Test all collection types at once
        var entity = new EntityWithAdvancedCollections
        {
            Name = "Complete Test"
        };

        entity.Tags.Add("tag1");
        entity.Tags.Add("tag2");

        entity.Numbers.Add(1);
        entity.Numbers.Add(2);

        entity.History.AddLast("h1");
        entity.History.AddLast("h2");

        entity.PendingItems.Enqueue("p1");
        entity.PendingItems.Enqueue("p2");

        entity.UndoStack.Push("u1");
        entity.UndoStack.Push("u2");

        entity.Addresses.Add(new Address { Street = "Street1" });
        entity.FavoriteCities.Add(new City { Name = "City1" });

        // Act
        var id = _db.AdvancedCollectionEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.AdvancedCollectionEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.Name.ShouldBe("Complete Test");
        retrieved.Tags.Count.ShouldBe(2);
        retrieved.Numbers.Count.ShouldBe(2);
        retrieved.History.Count.ShouldBe(2);
        retrieved.PendingItems.Count.ShouldBe(2);
        retrieved.UndoStack.Count.ShouldBe(2);
        retrieved.Addresses.Count().ShouldBe(1);
        retrieved.FavoriteCities.Count().ShouldBe(1);
    }

    #endregion

    #region Private Setters Tests

    /// <summary>
    /// Tests entity with private setters can be deserialized.
    /// </summary>
    [Fact]
    public void EntityWithPrivateSetters_CanBeDeserialized()
    {
        // Arrange
        var entity = EntityWithPrivateSetters.Create("John Doe", 30);

        // Act
        var id = _db.PrivateSetterEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.PrivateSetterEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.Id.ShouldBe(id);
        retrieved.Name.ShouldBe("John Doe");
        retrieved.Age.ShouldBe(30);
    }

    /// <summary>
    /// Tests entity with private setters update works.
    /// </summary>
    [Fact]
    public void EntityWithPrivateSetters_Update_Works()
    {
        // Arrange
        var entity1 = EntityWithPrivateSetters.Create("Alice", 25);
        var id1 = _db.PrivateSetterEntities.Insert(entity1);

        var entity2 = EntityWithPrivateSetters.Create("Bob", 35);
        entity2.GetType().GetProperty("Id")!.SetValue(entity2, id1); // Force same Id

        _db.PrivateSetterEntities.Update(entity2);
        _db.SaveChanges();

        // Act
        var retrieved = _db.PrivateSetterEntities.FindById(id1);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.Id.ShouldBe(id1);
        retrieved.Name.ShouldBe("Bob");
        retrieved.Age.ShouldBe(35);
    }

    /// <summary>
    /// Tests entity with private setters query works.
    /// </summary>
    [Fact]
    public void EntityWithPrivateSetters_Query_Works()
    {
        // Arrange
        var entity1 = EntityWithPrivateSetters.Create("Charlie", 20);
        var entity2 = EntityWithPrivateSetters.Create("Diana", 30);
        var entity3 = EntityWithPrivateSetters.Create("Eve", 40);

        _db.PrivateSetterEntities.Insert(entity1);
        _db.PrivateSetterEntities.Insert(entity2);
        _db.PrivateSetterEntities.Insert(entity3);
        _db.SaveChanges();

        // Act
        var adults = _db.PrivateSetterEntities.Find(e => e.Age >= 30).ToList();

        // Assert
        adults.Count.ShouldBe(2);
        adults.ShouldContain(e => e.Name == "Diana");
        adults.ShouldContain(e => e.Name == "Eve");
    }

    #endregion

    #region Init-Only Setters Tests

    /// <summary>
    /// Tests entity with init setters can be deserialized.
    /// </summary>
    [Fact]
    public void EntityWithInitSetters_CanBeDeserialized()
    {
        // Arrange
        var entity = new EntityWithInitSetters
        {
            Id = ObjectId.NewObjectId(),
            Name = "Jane Doe",
            Age = 28,
            CreatedAt = DateTime.UtcNow
        };

        // Act
        var id = _db.InitSetterEntities.Insert(entity);
        _db.SaveChanges();
        var retrieved = _db.InitSetterEntities.FindById(id);

        // Assert
        retrieved.ShouldNotBeNull();
        retrieved.Id.ShouldBe(id);
        retrieved.Name.ShouldBe("Jane Doe");
        retrieved.Age.ShouldBe(28);
    }

    /// <summary>
    /// Tests entity with init setters query works.
    /// </summary>
    [Fact]
    public void EntityWithInitSetters_Query_Works()
    {
        // Arrange
        var entity1 = new EntityWithInitSetters { Id = ObjectId.NewObjectId(), Name = "Alpha", Age = 20, CreatedAt = DateTime.UtcNow };
        var entity2 = new EntityWithInitSetters { Id = ObjectId.NewObjectId(), Name = "Beta", Age = 30, CreatedAt = DateTime.UtcNow };
        var entity3 = new EntityWithInitSetters { Id = ObjectId.NewObjectId(), Name = "Gamma", Age = 40, CreatedAt = DateTime.UtcNow };

        _db.InitSetterEntities.Insert(entity1);
        _db.InitSetterEntities.Insert(entity2);
        _db.InitSetterEntities.Insert(entity3);
        _db.SaveChanges();

        // Act
        var results = _db.InitSetterEntities.Find(e => e.Age > 25).ToList();

        // Assert
        results.Count.ShouldBe(2);
        results.ShouldContain(e => e.Name == "Beta");
        results.ShouldContain(e => e.Name == "Gamma");
    }

    #endregion

    /// <summary>
    /// Disposes the resources used by this instance.
    /// </summary>
    public void Dispose()
    {
        _db?.Dispose();

        if (File.Exists(_dbPath))
            File.Delete(_dbPath);
        if (File.Exists(_walPath))
            File.Delete(_walPath);
    }
}
