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

    [Fact]
    public void Create_WithValidData_ShouldCreateTask()
    {
        // Arrange
        var title = "Task 1";
        var description = "Task Description";
        var storyId = StoryId.Create();
        var priority = TaskPriority.High;
        var createdBy = UserId.Create();

        // Act
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), title, description, storyId, priority, createdBy);

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

    [Fact]
    public void Create_WithNullDescription_ShouldCreateTaskWithEmptyDescription()
    {
        // Arrange
        var title = "Task 1";
        string? description = null;
        var storyId = StoryId.Create();
        var priority = TaskPriority.Medium;
        var createdBy = UserId.Create();

        // Act
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), title, description!, storyId, priority, createdBy);

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

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

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

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

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

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

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

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

        // Act
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), title, "Description", storyId, priority, createdBy);

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

    [Fact]
    public void Create_WithDifferentPriorities_ShouldSetCorrectPriority()
    {
        // Arrange
        var storyId = StoryId.Create();
        var createdBy = UserId.Create();

        // Act
        var taskLow = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task Low", "Desc", storyId, TaskPriority.Low, createdBy);
        var taskMedium = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task Medium", "Desc", storyId, TaskPriority.Medium, createdBy);
        var taskHigh = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task High", "Desc", storyId, TaskPriority.High, createdBy);
        var taskUrgent = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task Urgent", "Desc", storyId, TaskPriority.Urgent, createdBy);

        // Assert
        taskLow.Priority.Should().Be(TaskPriority.Low);
        taskMedium.Priority.Should().Be(TaskPriority.Medium);
        taskHigh.Priority.Should().Be(TaskPriority.High);
        taskUrgent.Priority.Should().Be(TaskPriority.Urgent);
    }

    #endregion

    #region UpdateDetails Tests

    [Fact]
    public void UpdateDetails_WithValidData_ShouldUpdateTask()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Original Title", "Original Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
        var originalCreatedAt = task.CreatedAt;
        var newTitle = "Updated Title";
        var newDescription = "Updated Description";

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

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

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

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

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

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

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

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

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

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

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

    #endregion

    #region UpdateStatus Tests

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

        // Act
        task.UpdateStatus(newStatus);

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

    [Fact]
    public void UpdateStatus_ToAllStatuses_ShouldSucceed()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());

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

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

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

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

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

    #endregion

    #region AssignTo Tests

    [Fact]
    public void AssignTo_WithValidUserId_ShouldAssignTask()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
        var assigneeId = UserId.Create();

        // Act
        task.AssignTo(assigneeId);

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

    [Fact]
    public void AssignTo_ReassignToDifferentUser_ShouldUpdateAssignee()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
        var firstAssignee = UserId.Create();
        var secondAssignee = UserId.Create();

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

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

    #endregion

    #region UpdatePriority Tests

    [Fact]
    public void UpdatePriority_WithValidPriority_ShouldUpdatePriority()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Low, UserId.Create());
        var newPriority = TaskPriority.Urgent;

        // Act
        task.UpdatePriority(newPriority);

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

    [Fact]
    public void UpdatePriority_ToAllPriorities_ShouldSucceed()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());

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

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

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

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

    #endregion

    #region UpdateEstimate Tests

    [Fact]
    public void UpdateEstimate_WithValidHours_ShouldUpdateEstimate()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
        var hours = 4.5m;

        // Act
        task.UpdateEstimate(hours);

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

    [Fact]
    public void UpdateEstimate_WithZeroHours_ShouldSucceed()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        task.UpdateEstimate(0);

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

    [Fact]
    public void UpdateEstimate_WithNegativeHours_ShouldThrowDomainException()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());

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

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

    [Fact]
    public void UpdateEstimate_MultipleUpdates_ShouldOverwritePreviousValue()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        task.UpdateEstimate(4);
        task.UpdateEstimate(8);

        // Assert
        task.EstimatedHours.Should().Be(8);
    }

    #endregion

    #region LogActualHours Tests

    [Fact]
    public void LogActualHours_WithValidHours_ShouldLogHours()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());
        var hours = 5.5m;

        // Act
        task.LogActualHours(hours);

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

    [Fact]
    public void LogActualHours_WithZeroHours_ShouldSucceed()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        task.LogActualHours(0);

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

    [Fact]
    public void LogActualHours_WithNegativeHours_ShouldThrowDomainException()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());

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

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

    [Fact]
    public void LogActualHours_MultipleUpdates_ShouldOverwritePreviousValue()
    {
        // Arrange
        var task = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", StoryId.Create(), TaskPriority.Medium, UserId.Create());

        // Act
        task.LogActualHours(4);
        task.LogActualHours(6);

        // Assert
        task.ActualHours.Should().Be(6);
    }

    #endregion

    #region Entity Characteristics Tests

    [Fact]
    public void WorkTask_ShouldHaveUniqueId()
    {
        // Arrange & Act
        var storyId = StoryId.Create();
        var createdBy = UserId.Create();
        var task1 = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 1", "Description", storyId, TaskPriority.Medium, createdBy);
        var task2 = WorkTask.Create(TestDataBuilder.CreateTestTenantId(), "Task 2", "Description", storyId, TaskPriority.Medium, createdBy);

        // Assert
        task1.Id.Should().NotBe(task2.Id);
    }

    #endregion
}
