using FluentAssertions;
using Moq;
using ColaFlow.Modules.ProjectManagement.Application.Commands.UpdateTaskStatus;
using ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;

namespace ColaFlow.Application.Tests.Commands.UpdateTaskStatus;

/// <summary>
/// Tests for UpdateTaskStatusCommandHandler
/// </summary>
public class UpdateTaskStatusCommandHandlerTests
{
    private readonly Mock<IProjectRepository> _projectRepositoryMock;
    private readonly Mock<IUnitOfWork> _unitOfWorkMock;
    private readonly UpdateTaskStatusCommandHandler _handler;

    public UpdateTaskStatusCommandHandlerTests()
    {
        _projectRepositoryMock = new Mock<IProjectRepository>();
        _unitOfWorkMock = new Mock<IUnitOfWork>();
        _handler = new UpdateTaskStatusCommandHandler(_projectRepositoryMock.Object, _unitOfWorkMock.Object);
    }

    [Fact]
    public async Task Should_Update_Task_Status_To_InProgress_Successfully()
    {
        // Arrange
        var userId = UserId.Create();
        var project = Project.Create(TenantId.Create(Guid.NewGuid()), "Test Project", "Test Description", "TST", userId);
        var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
        var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
        var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
        var taskId = task.Id;

        _projectRepositoryMock
            .Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(project);

        var command = new UpdateTaskStatusCommand
        {
            TaskId = taskId.Value,
            NewStatus = "In Progress" // Display name with space
        };

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Id.Should().Be(taskId.Value);
        result.Status.Should().Be("In Progress");
        task.Status.Should().Be(WorkItemStatus.InProgress);

        _projectRepositoryMock.Verify(x => x.Update(project), Times.Once);
        _unitOfWorkMock.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

    [Fact]
    public async Task Should_Update_Task_Status_To_InProgress_With_CodeName_Successfully()
    {
        // Arrange - This tests the bug fix for accepting "InProgress" (without space)
        var userId = UserId.Create();
        var project = Project.Create(TenantId.Create(Guid.NewGuid()), "Test Project", "Test Description", "TST", userId);
        var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
        var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
        var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
        var taskId = task.Id;

        _projectRepositoryMock
            .Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(project);

        var command = new UpdateTaskStatusCommand
        {
            TaskId = taskId.Value,
            NewStatus = "InProgress" // Code name without space (this was causing the bug)
        };

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Id.Should().Be(taskId.Value);
        result.Status.Should().Be("In Progress");
        task.Status.Should().Be(WorkItemStatus.InProgress);
    }

    [Fact]
    public async Task Should_Update_Task_Status_To_Done_Successfully()
    {
        // Arrange
        var userId = UserId.Create();
        var project = Project.Create(TenantId.Create(Guid.NewGuid()), "Test Project", "Test Description", "TST", userId);
        var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
        var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
        var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
        var taskId = task.Id;

        _projectRepositoryMock
            .Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(project);

        var command = new UpdateTaskStatusCommand
        {
            TaskId = taskId.Value,
            NewStatus = "Done"
        };

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Status.Should().Be("Done");
        task.Status.Should().Be(WorkItemStatus.Done);
    }

    [Fact]
    public async Task Should_Update_Task_Status_To_Blocked_Successfully()
    {
        // Arrange
        var userId = UserId.Create();
        var project = Project.Create(TenantId.Create(Guid.NewGuid()), "Test Project", "Test Description", "TST", userId);
        var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
        var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
        var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
        var taskId = task.Id;

        _projectRepositoryMock
            .Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(project);

        var command = new UpdateTaskStatusCommand
        {
            TaskId = taskId.Value,
            NewStatus = "Blocked"
        };

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Status.Should().Be("Blocked");
        task.Status.Should().Be(WorkItemStatus.Blocked);
    }

    [Fact]
    public async Task Should_Update_Task_Status_To_InReview_Successfully()
    {
        // Arrange
        var userId = UserId.Create();
        var project = Project.Create(TenantId.Create(Guid.NewGuid()), "Test Project", "Test Description", "TST", userId);
        var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
        var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
        var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
        var taskId = task.Id;

        _projectRepositoryMock
            .Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(project);

        var command = new UpdateTaskStatusCommand
        {
            TaskId = taskId.Value,
            NewStatus = "In Review"
        };

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Status.Should().Be("In Review");
        task.Status.Should().Be(WorkItemStatus.InReview);
    }

    [Fact]
    public async Task Should_Throw_NotFoundException_When_Task_Not_Found()
    {
        // Arrange
        var taskId = TaskId.Create();
        _projectRepositoryMock
            .Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny<CancellationToken>()))
            .ReturnsAsync((Project?)null);

        var command = new UpdateTaskStatusCommand
        {
            TaskId = taskId.Value,
            NewStatus = "In Progress"
        };

        // Act
        Func<Task> act = async () => await _handler.Handle(command, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<NotFoundException>()
            .WithMessage("*Task*");
    }

    [Fact]
    public async Task Should_Throw_Exception_When_Moving_Done_To_ToDo()
    {
        // Arrange
        var userId = UserId.Create();
        var project = Project.Create(TenantId.Create(Guid.NewGuid()), "Test Project", "Test Description", "TST", userId);
        var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
        var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
        var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
        var taskId = task.Id;

        // First set task to Done
        task.UpdateStatus(WorkItemStatus.Done);

        _projectRepositoryMock
            .Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(project);

        var command = new UpdateTaskStatusCommand
        {
            TaskId = taskId.Value,
            NewStatus = "To Do"
        };

        // Act
        Func<Task> act = async () => await _handler.Handle(command, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<DomainException>()
            .WithMessage("*Cannot move a completed task back to ToDo*");
    }

    [Fact]
    public async Task Should_Allow_Blocked_To_Any_Status()
    {
        // Arrange
        var userId = UserId.Create();
        var project = Project.Create(TenantId.Create(Guid.NewGuid()), "Test Project", "Test Description", "TST", userId);
        var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
        var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
        var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
        var taskId = task.Id;

        // Set task to Blocked
        task.UpdateStatus(WorkItemStatus.Blocked);

        _projectRepositoryMock
            .Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(project);

        var command = new UpdateTaskStatusCommand
        {
            TaskId = taskId.Value,
            NewStatus = "In Progress"
        };

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Status.Should().Be("In Progress");
        task.Status.Should().Be(WorkItemStatus.InProgress);
    }

    [Fact]
    public async Task Should_Allow_Any_Status_To_Blocked()
    {
        // Arrange
        var userId = UserId.Create();
        var project = Project.Create(TenantId.Create(Guid.NewGuid()), "Test Project", "Test Description", "TST", userId);
        var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
        var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
        var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
        var taskId = task.Id;

        // Task starts as ToDo
        task.Status.Should().Be(WorkItemStatus.ToDo);

        _projectRepositoryMock
            .Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(project);

        var command = new UpdateTaskStatusCommand
        {
            TaskId = taskId.Value,
            NewStatus = "Blocked"
        };

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Status.Should().Be("Blocked");
        task.Status.Should().Be(WorkItemStatus.Blocked);
    }

    [Fact]
    public async Task Should_Throw_Exception_For_Invalid_Status()
    {
        // Arrange
        var userId = UserId.Create();
        var project = Project.Create(TenantId.Create(Guid.NewGuid()), "Test Project", "Test Description", "TST", userId);
        var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
        var story = epic.CreateStory("Test Story", "Story Description", TaskPriority.Medium, userId);
        var task = story.CreateTask("Test Task", "Task Description", TaskPriority.Medium, userId);
        var taskId = task.Id;

        _projectRepositoryMock
            .Setup(x => x.GetProjectWithTaskAsync(taskId, It.IsAny<CancellationToken>()))
            .ReturnsAsync(project);

        var command = new UpdateTaskStatusCommand
        {
            TaskId = taskId.Value,
            NewStatus = "InvalidStatus"
        };

        // Act
        Func<Task> act = async () => await _handler.Handle(command, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<InvalidOperationException>()
            .WithMessage("*InvalidStatus*");
    }
}
