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 Story entity
/// </summary>
public class StoryTests
{
    #region Create Tests

    [Fact]
    public void Create_WithValidData_ShouldCreateStory()
    {
        // Arrange
        var title = "User Story 1";
        var description = "Story Description";
        var epicId = EpicId.Create();
        var priority = TaskPriority.High;
        var createdBy = UserId.Create();

        // Act
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), title, description, epicId, priority, createdBy);

        // Assert
        story.Should().NotBeNull();
        story.Title.Should().Be(title);
        story.Description.Should().Be(description);
        story.EpicId.Should().Be(epicId);
        story.Status.Should().Be(WorkItemStatus.ToDo);
        story.Priority.Should().Be(priority);
        story.CreatedBy.Should().Be(createdBy);
        story.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
        story.UpdatedAt.Should().BeNull();
        story.EstimatedHours.Should().BeNull();
        story.ActualHours.Should().BeNull();
        story.AssigneeId.Should().BeNull();
        story.Tasks.Should().BeEmpty();
    }

    [Fact]
    public void Create_WithNullDescription_ShouldCreateStoryWithEmptyDescription()
    {
        // Arrange
        var title = "User Story 1";
        string? description = null;
        var epicId = EpicId.Create();
        var priority = TaskPriority.Medium;
        var createdBy = UserId.Create();

        // Act
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), title, description!, epicId, priority, createdBy);

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

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData(null)]
    public void Create_WithEmptyTitle_ShouldThrowDomainException(string invalidTitle)
    {
        // Arrange
        var epicId = EpicId.Create();
        var priority = TaskPriority.Medium;
        var createdBy = UserId.Create();

        // Act
        Action act = () => Story.Create(TestDataBuilder.CreateTestTenantId(), invalidTitle, "Description", epicId, priority, createdBy);

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

    [Fact]
    public void Create_WithTitleExceeding200Characters_ShouldThrowDomainException()
    {
        // Arrange
        var title = new string('A', 201);
        var epicId = EpicId.Create();
        var priority = TaskPriority.Medium;
        var createdBy = UserId.Create();

        // Act
        Action act = () => Story.Create(TestDataBuilder.CreateTestTenantId(), title, "Description", epicId, priority, createdBy);

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

    [Fact]
    public void Create_WithTitleExactly200Characters_ShouldSucceed()
    {
        // Arrange
        var title = new string('A', 200);
        var epicId = EpicId.Create();
        var priority = TaskPriority.Medium;
        var createdBy = UserId.Create();

        // Act
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), title, "Description", epicId, priority, createdBy);

        // Assert
        story.Should().NotBeNull();
        story.Title.Should().Be(title);
    }

    #endregion

    #region CreateTask Tests

    [Fact]
    public void CreateTask_WithValidData_ShouldCreateTask()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());
        var taskTitle = "Task 1";
        var taskDescription = "Task Description";
        var priority = TaskPriority.Urgent;
        var createdBy = UserId.Create();

        // Act
        var task = story.CreateTask(taskTitle, taskDescription, priority, createdBy);

        // Assert
        task.Should().NotBeNull();
        task.Title.Should().Be(taskTitle);
        task.Description.Should().Be(taskDescription);
        task.StoryId.Should().Be(story.Id);
        task.Priority.Should().Be(priority);
        task.CreatedBy.Should().Be(createdBy);
        story.Tasks.Should().ContainSingle();
        story.Tasks.Should().Contain(task);
    }

    [Fact]
    public void CreateTask_MultipleTasks_ShouldAddToCollection()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());
        var createdBy = UserId.Create();

        // Act
        var task1 = story.CreateTask("Task 1", "Desc 1", TaskPriority.Low, createdBy);
        var task2 = story.CreateTask("Task 2", "Desc 2", TaskPriority.Medium, createdBy);
        var task3 = story.CreateTask("Task 3", "Desc 3", TaskPriority.High, createdBy);

        // Assert
        story.Tasks.Should().HaveCount(3);
        story.Tasks.Should().Contain(new[] { task1, task2, task3 });
    }

    #endregion

    #region UpdateDetails Tests

    [Fact]
    public void UpdateDetails_WithValidData_ShouldUpdateStory()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Original Title", "Original Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());
        var originalCreatedAt = story.CreatedAt;
        var newTitle = "Updated Title";
        var newDescription = "Updated Description";

        // Act
        story.UpdateDetails(newTitle, newDescription);

        // Assert
        story.Title.Should().Be(newTitle);
        story.Description.Should().Be(newDescription);
        story.CreatedAt.Should().Be(originalCreatedAt);
        story.UpdatedAt.Should().NotBeNull();
        story.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void UpdateDetails_WithNullDescription_ShouldSetEmptyDescription()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Original Title", "Original Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        story.UpdateDetails("Updated Title", null!);

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

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData(null)]
    public void UpdateDetails_WithEmptyTitle_ShouldThrowDomainException(string invalidTitle)
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Original Title", "Original Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        Action act = () => story.UpdateDetails(invalidTitle, "Updated Description");

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

    [Fact]
    public void UpdateDetails_WithTitleExceeding200Characters_ShouldThrowDomainException()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Original Title", "Original Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());
        var title = new string('A', 201);

        // Act
        Action act = () => story.UpdateDetails(title, "Updated Description");

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

    #endregion

    #region UpdateStatus Tests

    [Fact]
    public void UpdateStatus_WithValidStatus_ShouldUpdateStatus()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());
        var newStatus = WorkItemStatus.InProgress;

        // Act
        story.UpdateStatus(newStatus);

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

    [Fact]
    public void UpdateStatus_ToAllStatuses_ShouldSucceed()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());

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

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

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

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

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

    #endregion

    #region AssignTo Tests

    [Fact]
    public void AssignTo_WithValidUserId_ShouldAssignStory()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());
        var assigneeId = UserId.Create();

        // Act
        story.AssignTo(assigneeId);

        // Assert
        story.AssigneeId.Should().Be(assigneeId);
        story.UpdatedAt.Should().NotBeNull();
        story.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void AssignTo_ReassignToDifferentUser_ShouldUpdateAssignee()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());
        var firstAssignee = UserId.Create();
        var secondAssignee = UserId.Create();

        // Act
        story.AssignTo(firstAssignee);
        story.AssignTo(secondAssignee);

        // Assert
        story.AssigneeId.Should().Be(secondAssignee);
    }

    #endregion

    #region UpdateEstimate Tests

    [Fact]
    public void UpdateEstimate_WithValidHours_ShouldUpdateEstimate()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());
        var hours = 8.5m;

        // Act
        story.UpdateEstimate(hours);

        // Assert
        story.EstimatedHours.Should().Be(hours);
        story.UpdatedAt.Should().NotBeNull();
        story.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void UpdateEstimate_WithZeroHours_ShouldSucceed()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        story.UpdateEstimate(0);

        // Assert
        story.EstimatedHours.Should().Be(0);
    }

    [Fact]
    public void UpdateEstimate_WithNegativeHours_ShouldThrowDomainException()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        Action act = () => story.UpdateEstimate(-1);

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Estimated hours cannot be negative");
    }

    [Fact]
    public void UpdateEstimate_MultipleUpdates_ShouldOverwritePreviousValue()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        story.UpdateEstimate(8);
        story.UpdateEstimate(16);

        // Assert
        story.EstimatedHours.Should().Be(16);
    }

    #endregion

    #region LogActualHours Tests

    [Fact]
    public void LogActualHours_WithValidHours_ShouldLogHours()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());
        var hours = 10.5m;

        // Act
        story.LogActualHours(hours);

        // Assert
        story.ActualHours.Should().Be(hours);
        story.UpdatedAt.Should().NotBeNull();
        story.UpdatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
    }

    [Fact]
    public void LogActualHours_WithZeroHours_ShouldSucceed()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        story.LogActualHours(0);

        // Assert
        story.ActualHours.Should().Be(0);
    }

    [Fact]
    public void LogActualHours_WithNegativeHours_ShouldThrowDomainException()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        Action act = () => story.LogActualHours(-1);

        // Assert
        act.Should().Throw<DomainException>()
            .WithMessage("Actual hours cannot be negative");
    }

    [Fact]
    public void LogActualHours_MultipleUpdates_ShouldOverwritePreviousValue()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        story.LogActualHours(8);
        story.LogActualHours(12);

        // Assert
        story.ActualHours.Should().Be(12);
    }

    #endregion

    #region Entity Characteristics Tests

    [Fact]
    public void Tasks_Collection_ShouldBeReadOnly()
    {
        // Arrange
        var story = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", EpicId.Create(), TaskPriority.Medium, UserId.Create());

        // Act & Assert
        story.Tasks.Should().BeAssignableTo<IReadOnlyCollection<WorkTask>>();
    }

    [Fact]
    public void Story_ShouldHaveUniqueId()
    {
        // Arrange & Act
        var epicId = EpicId.Create();
        var createdBy = UserId.Create();
        var story1 = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 1", "Description", epicId, TaskPriority.Medium, createdBy);
        var story2 = Story.Create(TestDataBuilder.CreateTestTenantId(), "Story 2", "Description", epicId, TaskPriority.Medium, createdBy);

        // Assert
        story1.Id.Should().NotBe(story2.Id);
    }

    #endregion
}
