using System;
using ZB.MOM.WW.CBDD.Bson;
using ZB.MOM.WW.CBDD.Shared;
using ZB.MOM.WW.CBDD.Tests;
using Xunit;

namespace ZB.MOM.WW.CBDD.Tests
{
    public class TemporalTypesTests : IDisposable
    {
        private readonly Shared.TestDbContext _db;
        private readonly string _dbPath;

        /// <summary>
        /// Initializes a new instance of the <see cref="TemporalTypesTests"/> class.
        /// </summary>
        public TemporalTypesTests()
        {
            _dbPath = $"temporal_test_{Guid.NewGuid()}.db";
            _db = new Shared.TestDbContext(_dbPath);
        }

        /// <summary>
        /// Releases test resources.
        /// </summary>
        public void Dispose()
        {
            _db?.Dispose();
            if (File.Exists(_dbPath))
                File.Delete(_dbPath);
        }

        /// <summary>
        /// Verifies temporal entity collection initialization.
        /// </summary>
        [Fact]
        public void TemporalEntity_Collection_IsInitialized()
        {
            _db.TemporalEntities.ShouldNotBeNull();
        }

        /// <summary>
        /// Verifies temporal fields round-trip through insert and lookup.
        /// </summary>
        [Fact]
        public void TemporalEntity_Insert_And_FindById_Works()
        {
            // Arrange
            var now = DateTime.UtcNow;
            var offset = DateTimeOffset.UtcNow;
            var duration = TimeSpan.FromHours(5.5);
            var birthDate = new DateOnly(1990, 5, 15);
            var openingTime = new TimeOnly(9, 30, 0);

            var entity = new TemporalEntity
            {
                Id = ObjectId.NewObjectId(),
                Name = "Test Entity",
                CreatedAt = now,
                UpdatedAt = offset,
                LastAccessedAt = offset.AddDays(1),
                Duration = duration,
                OptionalDuration = TimeSpan.FromMinutes(30),
                BirthDate = birthDate,
                Anniversary = new DateOnly(2020, 6, 10),
                OpeningTime = openingTime,
                ClosingTime = new TimeOnly(18, 0, 0)
            };

            // Act
            _db.TemporalEntities.Insert(entity);
            var retrieved = _db.TemporalEntities.FindById(entity.Id);

            // Assert
            retrieved.ShouldNotBeNull();
            retrieved.Name.ShouldBe(entity.Name);

            // DateTime comparison (allowing some millisecond precision loss)
            (retrieved.CreatedAt.Ticks / 10000).ShouldBe(entity.CreatedAt.Ticks / 10000); // millisecond precision

            // DateTimeOffset comparison
            (retrieved.UpdatedAt.UtcDateTime.Ticks / 10000).ShouldBe(entity.UpdatedAt.UtcDateTime.Ticks / 10000);
            retrieved.LastAccessedAt.ShouldNotBeNull();
            (retrieved.LastAccessedAt!.Value.UtcDateTime.Ticks / 10000).ShouldBe(entity.LastAccessedAt!.Value.UtcDateTime.Ticks / 10000);

            // TimeSpan comparison
            retrieved.Duration.ShouldBe(entity.Duration);
            retrieved.OptionalDuration.ShouldNotBeNull();
            retrieved.OptionalDuration!.Value.ShouldBe(entity.OptionalDuration!.Value);

            // DateOnly comparison
            retrieved.BirthDate.ShouldBe(entity.BirthDate);
            retrieved.Anniversary.ShouldNotBeNull();
            retrieved.Anniversary!.Value.ShouldBe(entity.Anniversary!.Value);

            // TimeOnly comparison
            retrieved.OpeningTime.ShouldBe(entity.OpeningTime);
            retrieved.ClosingTime.ShouldNotBeNull();
            retrieved.ClosingTime!.Value.ShouldBe(entity.ClosingTime!.Value);
        }

        /// <summary>
        /// Verifies insert behavior when optional temporal fields are null.
        /// </summary>
        [Fact]
        public void TemporalEntity_Insert_WithNullOptionalFields_Works()
        {
            // Arrange
            var entity = new TemporalEntity
            {
                Id = ObjectId.NewObjectId(),
                Name = "Minimal Entity",
                CreatedAt = DateTime.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow,
                Duration = TimeSpan.FromHours(1),
                BirthDate = new DateOnly(1985, 3, 20),
                OpeningTime = new TimeOnly(8, 0, 0),
                // Optional fields left null
                LastAccessedAt = null,
                OptionalDuration = null,
                Anniversary = null,
                ClosingTime = null
            };

            // Act
            _db.TemporalEntities.Insert(entity);
            var retrieved = _db.TemporalEntities.FindById(entity.Id);

            // Assert
            retrieved.ShouldNotBeNull();
            retrieved.Name.ShouldBe(entity.Name);
            retrieved.LastAccessedAt.ShouldBeNull();
            retrieved.OptionalDuration.ShouldBeNull();
            retrieved.Anniversary.ShouldBeNull();
            retrieved.ClosingTime.ShouldBeNull();
        }

        /// <summary>
        /// Verifies temporal entity updates persist correctly.
        /// </summary>
        [Fact]
        public void TemporalEntity_Update_Works()
        {
            // Arrange
            var entity = new TemporalEntity
            {
                Id = ObjectId.NewObjectId(),
                Name = "Original",
                CreatedAt = DateTime.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow,
                Duration = TimeSpan.FromHours(1),
                BirthDate = new DateOnly(1990, 1, 1),
                OpeningTime = new TimeOnly(9, 0, 0)
            };

            _db.TemporalEntities.Insert(entity);

            // Act - Update temporal fields
            entity.Name = "Updated";
            entity.UpdatedAt = DateTimeOffset.UtcNow.AddDays(1);
            entity.Duration = TimeSpan.FromHours(2);
            entity.BirthDate = new DateOnly(1991, 2, 2);
            entity.OpeningTime = new TimeOnly(10, 0, 0);

            _db.TemporalEntities.Update(entity);
            var retrieved = _db.TemporalEntities.FindById(entity.Id);

            // Assert
            retrieved.ShouldNotBeNull();
            retrieved.Name.ShouldBe("Updated");
            retrieved.Duration.ShouldBe(entity.Duration);
            retrieved.BirthDate.ShouldBe(entity.BirthDate);
            retrieved.OpeningTime.ShouldBe(entity.OpeningTime);
        }

        /// <summary>
        /// Verifies querying temporal entities by temporal fields.
        /// </summary>
        [Fact]
        public void TemporalEntity_Query_Works()
        {
            // Arrange
            var birthDate1 = new DateOnly(1990, 1, 1);
            var birthDate2 = new DateOnly(1995, 6, 15);

            var entity1 = new TemporalEntity
            {
                Id = ObjectId.NewObjectId(),
                Name = "Person 1",
                CreatedAt = DateTime.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow,
                Duration = TimeSpan.FromHours(1),
                BirthDate = birthDate1,
                OpeningTime = new TimeOnly(9, 0, 0)
            };

            var entity2 = new TemporalEntity
            {
                Id = ObjectId.NewObjectId(),
                Name = "Person 2",
                CreatedAt = DateTime.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow,
                Duration = TimeSpan.FromHours(2),
                BirthDate = birthDate2,
                OpeningTime = new TimeOnly(10, 0, 0)
            };

            _db.TemporalEntities.Insert(entity1);
            _db.TemporalEntities.Insert(entity2);

            // Act
            var results = _db.TemporalEntities.AsQueryable()
                .Where(e => e.BirthDate == birthDate1)
                .ToList();

            // Assert
            results.Count().ShouldBe(1);
            results[0].Name.ShouldBe("Person 1");
        }

        /// <summary>
        /// Verifies edge-case TimeSpan values are persisted correctly.
        /// </summary>
        [Fact]
        public void TimeSpan_EdgeCases_Work()
        {
            // Arrange - Test various TimeSpan values
            var entity = new TemporalEntity
            {
                Id = ObjectId.NewObjectId(),
                Name = "TimeSpan Test",
                CreatedAt = DateTime.UtcNow,
                UpdatedAt = DateTimeOffset.UtcNow,
                Duration = TimeSpan.Zero,
                OptionalDuration = TimeSpan.MaxValue,
                BirthDate = DateOnly.MinValue,
                OpeningTime = TimeOnly.MinValue
            };

            // Act
            _db.TemporalEntities.Insert(entity);
            var retrieved = _db.TemporalEntities.FindById(entity.Id);

            // Assert
            retrieved.ShouldNotBeNull();
            retrieved.Duration.ShouldBe(TimeSpan.Zero);
            retrieved.OptionalDuration.ShouldNotBeNull();
            retrieved.OptionalDuration!.Value.ShouldBe(TimeSpan.MaxValue);
            retrieved.BirthDate.ShouldBe(DateOnly.MinValue);
            retrieved.OpeningTime.ShouldBe(TimeOnly.MinValue);
        }
    }
}
