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

namespace ZB.MOM.WW.CBDD.Tests;

public class DocumentCollectionTests : IDisposable
{
    private readonly TestDbContext _db;
    private readonly string _dbPath;
    private readonly string _walPath;

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

        _db = new TestDbContext(_dbPath);
    }

    /// <summary>
    ///     Releases test resources.
    /// </summary>
    public void Dispose()
    {
        _db?.Dispose();
    }

    /// <summary>
    ///     Verifies insert and find-by-id operations.
    /// </summary>
    [Fact]
    public void Insert_And_FindById_Works()
    {
        // Arrange
        var user = new User { Name = "Alice", Age = 30 };

        // Act
        var id = _db.Users.Insert(user);
        _db.SaveChanges();
        var found = _db.Users.FindById(id);

        // Assert
        found.ShouldNotBeNull();
        found.Id.ShouldBe(id);
        found.Name.ShouldBe("Alice");
        found.Age.ShouldBe(30);
    }

    /// <summary>
    ///     Verifies find-by-id returns null when no document is found.
    /// </summary>
    [Fact]
    public void FindById_Returns_Null_When_Not_Found()
    {
        // Act
        var found = _db.Users.FindById(ObjectId.NewObjectId());

        // Assert
        found.ShouldBeNull();
    }

    /// <summary>
    ///     Verifies find-all returns all entities.
    /// </summary>
    [Fact]
    public void FindAll_Returns_All_Entities()
    {
        // Arrange
        _db.Users.Insert(new User { Name = "Alice", Age = 30 });
        _db.Users.Insert(new User { Name = "Bob", Age = 25 });
        _db.Users.Insert(new User { Name = "Charlie", Age = 35 });
        _db.SaveChanges();

        // Act
        var all = _db.Users.FindAll().ToList();

        // Assert
        all.Count.ShouldBe(3);
        all.ShouldContain(u => u.Name == "Alice");
        all.ShouldContain(u => u.Name == "Bob");
        all.ShouldContain(u => u.Name == "Charlie");
    }

    /// <summary>
    ///     Verifies update modifies an existing entity.
    /// </summary>
    [Fact]
    public void Update_Modifies_Entity()
    {
        // Arrange
        var user = new User { Name = "Alice", Age = 30 };
        var id = _db.Users.Insert(user);
        _db.SaveChanges();

        // Act
        user.Age = 31;
        bool updated = _db.Users.Update(user);
        _db.SaveChanges();

        // Assert
        updated.ShouldBeTrue();

        var found = _db.Users.FindById(id);
        found.ShouldNotBeNull();
        found.Age.ShouldBe(31);
    }

    /// <summary>
    ///     Verifies update returns false when the entity does not exist.
    /// </summary>
    [Fact]
    public void Update_Returns_False_When_Not_Found()
    {
        // Arrange
        var user = new User { Id = ObjectId.NewObjectId(), Name = "Ghost", Age = 99 };

        // Act
        bool updated = _db.Users.Update(user);
        _db.SaveChanges();

        // Assert
        updated.ShouldBeFalse();
    }

    /// <summary>
    ///     Verifies delete removes an entity.
    /// </summary>
    [Fact]
    public void Delete_Removes_Entity()
    {
        // Arrange
        var user = new User { Name = "Alice", Age = 30 };
        var id = _db.Users.Insert(user);
        _db.SaveChanges();

        // Act
        bool deleted = _db.Users.Delete(id);
        _db.SaveChanges();

        // Assert
        deleted.ShouldBeTrue();
        _db.Users.FindById(id).ShouldBeNull();
    }

    /// <summary>
    ///     Verifies delete returns false when the entity does not exist.
    /// </summary>
    [Fact]
    public void Delete_Returns_False_When_Not_Found()
    {
        // Act
        bool deleted = _db.Users.Delete(ObjectId.NewObjectId());
        _db.SaveChanges();

        // Assert
        deleted.ShouldBeFalse();
    }

    /// <summary>
    ///     Verifies count returns the correct entity count.
    /// </summary>
    [Fact]
    public void Count_Returns_Correct_Count()
    {
        // Arrange
        _db.Users.Insert(new User { Name = "Alice", Age = 30 });
        _db.Users.Insert(new User { Name = "Bob", Age = 25 });
        _db.SaveChanges();

        // Act
        int count = _db.Users.Count();

        // Assert
        count.ShouldBe(2);
    }

    /// <summary>
    ///     Verifies predicate queries filter entities correctly.
    /// </summary>
    [Fact]
    public void Find_With_Predicate_Filters_Correctly()
    {
        // Arrange
        _db.Users.Insert(new User { Name = "Alice", Age = 30 });
        _db.Users.Insert(new User { Name = "Bob", Age = 25 });
        _db.Users.Insert(new User { Name = "Charlie", Age = 35 });
        _db.SaveChanges();

        // Act
        var over30 = _db.Users.Find(u => u.Age > 30).ToList();

        // Assert
        over30.Count().ShouldBe(1);
        over30[0].Name.ShouldBe("Charlie");
    }

    /// <summary>
    ///     Verifies bulk insert stores multiple entities.
    /// </summary>
    [Fact]
    public void InsertBulk_Inserts_Multiple_Entities()
    {
        // Arrange
        var users = new[]
        {
            new User { Name = "User1", Age = 20 },
            new User { Name = "User2", Age = 21 },
            new User { Name = "User3", Age = 22 }
        };

        // Act
        var count = _db.Users.InsertBulk(users);
        _db.SaveChanges();

        // Assert
        count.Count.ShouldBe(3);
        _db.Users.Count().ShouldBe(3);
    }

    /// <summary>
    ///     Verifies inserts preserve an explicitly assigned identifier.
    /// </summary>
    [Fact]
    public void Insert_With_SpecifiedId_RetainsId()
    {
        // Arrange
        var id = ObjectId.NewObjectId();
        var user = new User { Id = id, Name = "SpecifiedID", Age = 40 };

        // Act
        var insertedId = _db.Users.Insert(user);
        _db.SaveChanges();

        // Assert
        insertedId.ShouldBe(id);

        var found = _db.Users.FindById(id);
        found.ShouldNotBeNull();
        found.Id.ShouldBe(id);
        found.Name.ShouldBe("SpecifiedID");
    }
}