using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
using ColaFlow.Domain.Tests.Builders;
using FluentAssertions;

namespace ColaFlow.Domain.Tests.Aggregates;

/// <summary>
/// Unit tests for Epic entity
/// </summary>
public class EpicTests
{
    #region Create Tests

    [Fact]
    public void Create_WithValidData_ShouldCreateEpic()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var name = "Epic 1";
        var description = "Epic Description";
        var projectId = ProjectId.Create();
        var createdBy = UserId.Create();

        // Act
        var epic = Epic.Create(tenantId, name, description, projectId, createdBy);

        // Assert
        epic.Should().NotBeNull();
        epic.TenantId.Should().Be(tenantId);
        epic.Name.Should().Be(name);
        epic.Description.Should().Be(description);
        epic.ProjectId.Should().Be(projectId);
        epic.Status.Should().Be(WorkItemStatus.ToDo);
        epic.Priority.Should().Be(TaskPriority.Medium);
        epic.CreatedBy.Should().Be(createdBy);
        epic.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
        epic.UpdatedAt.Should().BeNull();
        epic.Stories.Should().BeEmpty();
    }

    [Fact]
    public void Create_WithNullDescription_ShouldCreateEpicWithEmptyDescription()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var name = "Epic 1";
        string? description = null;
        var projectId = ProjectId.Create();
        var createdBy = UserId.Create();

        // Act
        var epic = Epic.Create(tenantId, name, description!, projectId, createdBy);

        // Assert
        epic.Should().NotBeNull();
        epic.Description.Should().Be(string.Empty);
    }

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData(null)]
    public void Create_WithEmptyName_ShouldThrowDomainException(string invalidName)
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var projectId = ProjectId.Create();
        var createdBy = UserId.Create();

        // Act
        Action act = () => Epic.Create(tenantId, invalidName, "Description", projectId, createdBy);

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Epic name cannot be empty");
    }

    [Fact]
    public void Create_WithNameExceeding200Characters_ShouldThrowDomainException()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var name = new string('A', 201);
        var projectId = ProjectId.Create();
        var createdBy = UserId.Create();

        // Act
        Action act = () => Epic.Create(tenantId, name, "Description", projectId, createdBy);

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Epic name cannot exceed 200 characters");
    }

    [Fact]
    public void Create_WithNameExactly200Characters_ShouldSucceed()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var name = new string('A', 200);
        var projectId = ProjectId.Create();
        var createdBy = UserId.Create();

        // Act
        var epic = Epic.Create(tenantId, name, "Description", projectId, createdBy);

        // Assert
        epic.Should().NotBeNull();
        epic.Name.Should().Be(name);
    }

    #endregion

    #region CreateStory Tests

    [Fact]
    public void CreateStory_WithValidData_ShouldCreateStory()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var epic = Epic.Create(tenantId, "Epic 1", "Description", ProjectId.Create(), UserId.Create());
        var storyTitle = "Story 1";
        var storyDescription = "Story Description";
        var priority = TaskPriority.High;
        var createdBy = UserId.Create();

        // Act
        var story = epic.CreateStory(storyTitle, storyDescription, priority, createdBy);

        // Assert
        story.Should().NotBeNull();
        story.Title.Should().Be(storyTitle);
        story.Description.Should().Be(storyDescription);
        story.EpicId.Should().Be(epic.Id);
        story.Priority.Should().Be(priority);
        story.CreatedBy.Should().Be(createdBy);
        epic.Stories.Should().ContainSingle();
        epic.Stories.Should().Contain(story);
    }

    [Fact]
    public void CreateStory_MultipleStories_ShouldAddToCollection()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var epic = Epic.Create(tenantId, "Epic 1", "Description", ProjectId.Create(), UserId.Create());
        var createdBy = UserId.Create();

        // Act
        var story1 = epic.CreateStory("Story 1", "Desc 1", TaskPriority.Low, createdBy);
        var story2 = epic.CreateStory("Story 2", "Desc 2", TaskPriority.Medium, createdBy);
        var story3 = epic.CreateStory("Story 3", "Desc 3", TaskPriority.High, createdBy);

        // Assert
        epic.Stories.Should().HaveCount(3);
        epic.Stories.Should().Contain(new[] { story1, story2, story3 });
    }

    #endregion

    #region UpdateDetails Tests

    [Fact]
    public void UpdateDetails_WithValidData_ShouldUpdateEpic()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var epic = Epic.Create(tenantId, "Original Name", "Original Description", ProjectId.Create(), UserId.Create());
        var originalCreatedAt = epic.CreatedAt;
        var newName = "Updated Name";
        var newDescription = "Updated Description";

        // Act
        epic.UpdateDetails(newName, newDescription);

        // Assert
        epic.Name.Should().Be(newName);
        epic.Description.Should().Be(newDescription);
        epic.CreatedAt.Should().Be(originalCreatedAt);
        epic.UpdatedAt.Should().NotBeNull();
        epic.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void UpdateDetails_WithNullDescription_ShouldSetEmptyDescription()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var epic = Epic.Create(tenantId, "Original Name", "Original Description", ProjectId.Create(), UserId.Create());

        // Act
        epic.UpdateDetails("Updated Name", null!);

        // Assert
        epic.Description.Should().Be(string.Empty);
    }

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData(null)]
    public void UpdateDetails_WithEmptyName_ShouldThrowDomainException(string invalidName)
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var epic = Epic.Create(tenantId, "Original Name", "Original Description", ProjectId.Create(), UserId.Create());

        // Act
        Action act = () => epic.UpdateDetails(invalidName, "Updated Description");

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Epic name cannot be empty");
    }

    [Fact]
    public void UpdateDetails_WithNameExceeding200Characters_ShouldThrowDomainException()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var epic = Epic.Create(tenantId, "Original Name", "Original Description", ProjectId.Create(), UserId.Create());
        var name = new string('A', 201);

        // Act
        Action act = () => epic.UpdateDetails(name, "Updated Description");

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Epic name cannot exceed 200 characters");
    }

    #endregion

    #region UpdateStatus Tests

    [Fact]
    public void UpdateStatus_WithValidStatus_ShouldUpdateStatus()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var epic = Epic.Create(tenantId, "Epic 1", "Description", ProjectId.Create(), UserId.Create());
        var newStatus = WorkItemStatus.InProgress;

        // Act
        epic.UpdateStatus(newStatus);

        // Assert
        epic.Status.Should().Be(newStatus);
        epic.UpdatedAt.Should().NotBeNull();
        epic.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void UpdateStatus_ToAllStatuses_ShouldSucceed()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var epic = Epic.Create(tenantId, "Epic 1", "Description", ProjectId.Create(), UserId.Create());

        // Act & Assert
        epic.UpdateStatus(WorkItemStatus.InProgress);
        epic.Status.Should().Be(WorkItemStatus.InProgress);

        epic.UpdateStatus(WorkItemStatus.InReview);
        epic.Status.Should().Be(WorkItemStatus.InReview);

        epic.UpdateStatus(WorkItemStatus.Done);
        epic.Status.Should().Be(WorkItemStatus.Done);

        epic.UpdateStatus(WorkItemStatus.Blocked);
        epic.Status.Should().Be(WorkItemStatus.Blocked);

        epic.UpdateStatus(WorkItemStatus.ToDo);
        epic.Status.Should().Be(WorkItemStatus.ToDo);
    }

    #endregion

    #region UpdatePriority Tests

    [Fact]
    public void UpdatePriority_WithValidPriority_ShouldUpdatePriority()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var epic = Epic.Create(tenantId, "Epic 1", "Description", ProjectId.Create(), UserId.Create());
        var newPriority = TaskPriority.Urgent;

        // Act
        epic.UpdatePriority(newPriority);

        // Assert
        epic.Priority.Should().Be(newPriority);
        epic.UpdatedAt.Should().NotBeNull();
        epic.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void UpdatePriority_ToAllPriorities_ShouldSucceed()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var epic = Epic.Create(tenantId, "Epic 1", "Description", ProjectId.Create(), UserId.Create());

        // Act & Assert
        epic.UpdatePriority(TaskPriority.Low);
        epic.Priority.Should().Be(TaskPriority.Low);

        epic.UpdatePriority(TaskPriority.Medium);
        epic.Priority.Should().Be(TaskPriority.Medium);

        epic.UpdatePriority(TaskPriority.High);
        epic.Priority.Should().Be(TaskPriority.High);

        epic.UpdatePriority(TaskPriority.Urgent);
        epic.Priority.Should().Be(TaskPriority.Urgent);
    }

    #endregion

    #region Entity Characteristics Tests

    [Fact]
    public void Stories_Collection_ShouldBeReadOnly()
    {
        // Arrange
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var epic = Epic.Create(tenantId, "Epic 1", "Description", ProjectId.Create(), UserId.Create());

        // Act & Assert
        epic.Stories.Should().BeAssignableTo<IReadOnlyCollection<Story>>();
    }

    [Fact]
    public void Epic_ShouldHaveUniqueId()
    {
        // Arrange & Act
        var tenantId = TestDataBuilder.CreateTestTenantId();
        var projectId = ProjectId.Create();
        var createdBy = UserId.Create();
        var epic1 = Epic.Create(tenantId, "Epic 1", "Description", projectId, createdBy);
        var epic2 = Epic.Create(tenantId, "Epic 2", "Description", projectId, createdBy);

        // Assert
        epic1.Id.Should().NotBe(epic2.Id);
    }

    #endregion
}
