using FluentAssertions;
using Moq;
using ColaFlow.Modules.ProjectManagement.Application.Commands.CreateStory;
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.CreateStory;

public class CreateStoryCommandHandlerTests
{
    private readonly Mock<IProjectRepository> _projectRepositoryMock;
    private readonly Mock<IUnitOfWork> _unitOfWorkMock;
    private readonly CreateStoryCommandHandler _handler;

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

    [Fact]
    public async Task Should_Create_Story_Successfully()
    {
        // Arrange
        var userId = UserId.Create();
        var project = Project.Create(TenantId.Create(Guid.NewGuid()), "Test Project", "Description", "TST", userId);
        var epic = project.CreateEpic("Test Epic", "Epic Description", userId);
        var epicId = epic.Id;

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

        var command = new CreateStoryCommand
        {
            EpicId = epicId.Value,
            Title = "New Story",
            Description = "Story Description",
            Priority = "High",
            EstimatedHours = 8,
            AssigneeId = userId.Value,
            CreatedBy = userId.Value
        };

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

        // Assert
        result.Should().NotBeNull();
        result.Title.Should().Be("New Story");
        result.Description.Should().Be("Story Description");
        result.EpicId.Should().Be(epicId.Value);
        result.Status.Should().Be("To Do");
        result.Priority.Should().Be("High");
        result.EstimatedHours.Should().Be(8);
        result.AssigneeId.Should().Be(userId.Value);

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

    [Fact]
    public async Task Should_Fail_When_Epic_Not_Found()
    {
        // Arrange
        var epicId = EpicId.Create();
        _projectRepositoryMock
            .Setup(x => x.GetProjectWithEpicAsync(epicId, It.IsAny<CancellationToken>()))
            .ReturnsAsync((Project?)null);

        var command = new CreateStoryCommand
        {
            EpicId = epicId.Value,
            Title = "New Story",
            Description = "Description",
            Priority = "Medium",
            CreatedBy = Guid.NewGuid()
        };

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

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

    [Fact]
    public async Task Should_Set_Default_Status_To_ToDo()
    {
        // Arrange
        var userId = UserId.Create();
        var project = Project.Create(TenantId.Create(Guid.NewGuid()), "Test Project", "Description", "TST", userId);
        var epic = project.CreateEpic("Test Epic", "Epic Description", userId);

        _projectRepositoryMock
            .Setup(x => x.GetProjectWithEpicAsync(epic.Id, It.IsAny<CancellationToken>()))
            .ReturnsAsync(project);

        var command = new CreateStoryCommand
        {
            EpicId = epic.Id.Value,
            Title = "New Story",
            Description = "Description",
            Priority = "Low",
            CreatedBy = userId.Value
        };

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

        // Assert
        result.Status.Should().Be("To Do");
    }
}
