using ColaFlow.Modules.ProjectManagement.Domain.Events;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using FluentAssertions;

namespace ColaFlow.Domain.Tests.Events;

/// <summary>
/// Unit tests for Domain Events
/// </summary>
public class DomainEventsTests
{
    #region ProjectCreatedEvent Tests

    [Fact]
    public void ProjectCreatedEvent_Constructor_ShouldSetProperties()
    {
        // Arrange
        var projectId = ProjectId.Create();
        var projectName = "Test Project";
        var createdBy = UserId.Create();

        // Act
        var @event = new ProjectCreatedEvent(projectId, TenantId.Create(Guid.NewGuid()), projectName, createdBy);

        // Assert
        @event.ProjectId.Should().Be(projectId);
        @event.ProjectName.Should().Be(projectName);
        @event.CreatedBy.Should().Be(createdBy);
        @event.OccurredOn.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void ProjectCreatedEvent_ShouldBeRecord()
    {
        // Arrange
        var projectId = ProjectId.Create();
        var projectName = "Test Project";
        var createdBy = UserId.Create();

        // Act
        var tenantId = TenantId.Create(Guid.NewGuid());
        var event1 = new ProjectCreatedEvent(projectId, tenantId, projectName, createdBy);
        var event2 = new ProjectCreatedEvent(projectId, tenantId, projectName, createdBy);

        // Assert - Records with same values should be equal
        event1.ProjectId.Should().Be(event2.ProjectId);
        event1.ProjectName.Should().Be(event2.ProjectName);
        event1.CreatedBy.Should().Be(event2.CreatedBy);
    }

    #endregion

    #region ProjectUpdatedEvent Tests

    [Fact]
    public void ProjectUpdatedEvent_Constructor_ShouldSetProperties()
    {
        // Arrange
        var projectId = ProjectId.Create();
        var name = "Updated Project";
        var description = "Updated Description";

        // Act
        var @event = new ProjectUpdatedEvent(projectId, name, description);

        // Assert
        @event.ProjectId.Should().Be(projectId);
        @event.Name.Should().Be(name);
        @event.Description.Should().Be(description);
        @event.OccurredOn.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void ProjectUpdatedEvent_WithNullDescription_ShouldAcceptNull()
    {
        // Arrange
        var projectId = ProjectId.Create();
        var name = "Updated Project";

        // Act
        var @event = new ProjectUpdatedEvent(projectId, name, null!);

        // Assert
        @event.Description.Should().BeNull();
    }

    #endregion

    #region ProjectArchivedEvent Tests

    [Fact]
    public void ProjectArchivedEvent_Constructor_ShouldSetProperties()
    {
        // Arrange
        var projectId = ProjectId.Create();

        // Act
        var @event = new ProjectArchivedEvent(projectId);

        // Assert
        @event.ProjectId.Should().Be(projectId);
        @event.OccurredOn.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void ProjectArchivedEvent_ShouldBeRecord()
    {
        // Arrange
        var projectId = ProjectId.Create();

        // Act
        var event1 = new ProjectArchivedEvent(projectId);
        var event2 = new ProjectArchivedEvent(projectId);

        // Assert - Records with same values should be equal
        event1.ProjectId.Should().Be(event2.ProjectId);
    }

    #endregion

    #region EpicCreatedEvent Tests

    [Fact]
    public void EpicCreatedEvent_Constructor_ShouldSetProperties()
    {
        // Arrange
        var epicId = EpicId.Create();
        var tenantId = TenantId.Create(Guid.NewGuid());
        var projectId = ProjectId.Create();
        var epicName = "Epic 1";

        // Act
        var @event = new EpicCreatedEvent(epicId, tenantId, projectId, epicName);

        // Assert
        @event.EpicId.Should().Be(epicId);
        @event.TenantId.Should().Be(tenantId);
        @event.ProjectId.Should().Be(projectId);
        @event.EpicName.Should().Be(epicName);
        @event.OccurredOn.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void EpicCreatedEvent_ShouldBeRecord()
    {
        // Arrange
        var epicId = EpicId.Create();
        var tenantId = TenantId.Create(Guid.NewGuid());
        var projectId = ProjectId.Create();
        var epicName = "Epic 1";

        // Act
        var event1 = new EpicCreatedEvent(epicId, tenantId, projectId, epicName);
        var event2 = new EpicCreatedEvent(epicId, tenantId, projectId, epicName);

        // Assert - Records with same values should be equal
        event1.EpicId.Should().Be(event2.EpicId);
        event1.TenantId.Should().Be(event2.TenantId);
        event1.ProjectId.Should().Be(event2.ProjectId);
        event1.EpicName.Should().Be(event2.EpicName);
    }

    #endregion

    #region Event Timing Tests

    [Fact]
    public void DomainEvents_OccurredOn_ShouldBeUtcTime()
    {
        // Arrange & Act
        var projectCreatedEvent = new ProjectCreatedEvent(ProjectId.Create(), TenantId.Create(Guid.NewGuid()), "Test", UserId.Create());
        var projectUpdatedEvent = new ProjectUpdatedEvent(ProjectId.Create(), "Test", "Desc");
        var projectArchivedEvent = new ProjectArchivedEvent(ProjectId.Create());
        var epicCreatedEvent = new EpicCreatedEvent(EpicId.Create(), TenantId.Create(Guid.NewGuid()), ProjectId.Create(), "Epic");

        // Assert
        projectCreatedEvent.OccurredOn.Kind.Should().Be(DateTimeKind.Utc);
        projectUpdatedEvent.OccurredOn.Kind.Should().Be(DateTimeKind.Utc);
        projectArchivedEvent.OccurredOn.Kind.Should().Be(DateTimeKind.Utc);
        epicCreatedEvent.OccurredOn.Kind.Should().Be(DateTimeKind.Utc);
    }

    [Fact]
    public void DomainEvents_OccurredOn_ShouldBeSetAutomatically()
    {
        // Arrange
        var beforeCreation = DateTime.UtcNow;

        // Act
        var @event = new ProjectCreatedEvent(ProjectId.Create(), TenantId.Create(Guid.NewGuid()), "Test", UserId.Create());

        // Assert
        var afterCreation = DateTime.UtcNow;
        @event.OccurredOn.Should().BeOnOrAfter(beforeCreation);
        @event.OccurredOn.Should().BeOnOrBefore(afterCreation);
    }

    #endregion

    #region Event Immutability Tests

    [Fact]
    public void DomainEvents_ShouldBeImmutable()
    {
        // Arrange
        var projectId = ProjectId.Create();
        var projectName = "Test Project";
        var createdBy = UserId.Create();

        // Act
        var @event = new ProjectCreatedEvent(projectId, TenantId.Create(Guid.NewGuid()), projectName, createdBy);
        var originalProjectId = @event.ProjectId;
        var originalProjectName = @event.ProjectName;
        var originalCreatedBy = @event.CreatedBy;
        var originalOccurredOn = @event.OccurredOn;

        // Try to access properties multiple times
        var projectId1 = @event.ProjectId;
        var projectName1 = @event.ProjectName;
        var createdBy1 = @event.CreatedBy;
        var occurredOn1 = @event.OccurredOn;

        // Assert - Properties should not change
        projectId1.Should().Be(originalProjectId);
        projectName1.Should().Be(originalProjectName);
        createdBy1.Should().Be(originalCreatedBy);
        occurredOn1.Should().Be(originalOccurredOn);
    }

    #endregion
}
