using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Workflow.Application.Features.WorkflowDefinitions.Commands;
using Workflow.Application.Features.WorkflowDefinitions.Queries;
using Workflow.Domain.Entities;
using Workflow.Domain.Enums;
using Workflow.Domain.Exceptions;
using Workflow.Infrastructure.Persistence;
using Xunit;

namespace Workflow.Tests.Handlers;

public class WorkflowDefinitionHandlerTests
{
    private static WorkflowDbContext CreateDbContext()
    {
        var options = new DbContextOptionsBuilder<WorkflowDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        return new WorkflowDbContext(options);
    }

    #region CreateWorkflowDefinition

    [Fact]
    public async Task CreateDefinition_SavesToDatabase()
    {
        // Arrange
        await using var db = CreateDbContext();
        var handler = new CreateWorkflowDefinitionCommandHandler(db);
        var command = new CreateWorkflowDefinitionCommand(
            Name: "Leave Approval",
            Code: "leave-approval",
            Description: "Standard leave approval workflow"
        );

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

        // Assert
        result.Should().NotBeNull();
        result.Name.Should().Be("Leave Approval");
        result.Code.Should().Be("leave-approval");
        result.Description.Should().Be("Standard leave approval workflow");
        result.Status.Should().Be(DefinitionStatus.Draft);
        result.Version.Should().Be(1);

        var entity = await db.WorkflowDefinitions.FirstOrDefaultAsync(d => d.Code == "leave-approval");
        entity.Should().NotBeNull();
        entity!.Name.Should().Be("Leave Approval");
    }

    [Fact]
    public async Task CreateDefinition_WithDuplicateCode_ThrowsBusinessException()
    {
        // Arrange
        await using var db = CreateDbContext();
        var existing = new WorkflowDefinition
        {
            Id = Guid.NewGuid(),
            Name = "Existing",
            Code = "duplicate-code",
            Status = DefinitionStatus.Draft,
            Version = 1
        };
        db.WorkflowDefinitions.Add(existing);
        await db.SaveChangesAsync();

        var handler = new CreateWorkflowDefinitionCommandHandler(db);
        var command = new CreateWorkflowDefinitionCommand(
            Name: "New Workflow",
            Code: "duplicate-code",
            Description: "Should fail"
        );

        // Act
        var act = () => handler.Handle(command, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<BusinessException>()
            .WithMessage("*duplicate-code*");
    }

    #endregion

    #region UpdateWorkflowDefinition

    [Fact]
    public async Task UpdateDefinition_UpdatesFields()
    {
        // Arrange
        await using var db = CreateDbContext();
        var definitionId = Guid.NewGuid();
        var existing = new WorkflowDefinition
        {
            Id = definitionId,
            Name = "Old Name",
            Code = "test-workflow",
            Description = "Old description",
            Status = DefinitionStatus.Draft,
            Version = 1
        };
        db.WorkflowDefinitions.Add(existing);
        await db.SaveChangesAsync();

        var handler = new UpdateWorkflowDefinitionCommandHandler(db);
        var command = new UpdateWorkflowDefinitionCommand(
            Id: definitionId,
            Name: "Updated Name",
            Description: "Updated description",
            DefinitionJson: "{\"nodes\":[],\"edges\":[]}"
        );

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

        // Assert
        result.Should().NotBeNull();
        result.Name.Should().Be("Updated Name");
        result.Description.Should().Be("Updated description");
        result.DefinitionJson.Should().Be("{\"nodes\":[],\"edges\":[]}");

        var entity = await db.WorkflowDefinitions.FindAsync(definitionId);
        entity.Should().NotBeNull();
        entity!.Name.Should().Be("Updated Name");
    }

    #endregion

    #region DeleteWorkflowDefinition

    [Fact]
    public async Task DeleteDefinition_SoftDeletes()
    {
        // Arrange
        await using var db = CreateDbContext();
        var definitionId = Guid.NewGuid();
        var existing = new WorkflowDefinition
        {
            Id = definitionId,
            Name = "To Delete",
            Code = "to-delete",
            Status = DefinitionStatus.Draft,
            Version = 1
        };
        db.WorkflowDefinitions.Add(existing);
        await db.SaveChangesAsync();

        var handler = new DeleteWorkflowDefinitionCommandHandler(db);
        var command = new DeleteWorkflowDefinitionCommand(Id: definitionId);

        // Act
        await handler.Handle(command, CancellationToken.None);

        // Assert - soft delete means the entity still exists but IsDeleted = true
        var entity = await db.WorkflowDefinitions
            .IgnoreQueryFilters()
            .FirstOrDefaultAsync(d => d.Id == definitionId);
        entity.Should().NotBeNull();
        entity!.IsDeleted.Should().BeTrue();

        // Normal query should not return the deleted entity
        var activeEntity = await db.WorkflowDefinitions.FindAsync(definitionId);
        activeEntity.Should().BeNull();
    }

    #endregion

    #region PublishWorkflowDefinition

    [Fact]
    public async Task PublishDefinition_ChangesStatusAndIncrementsVersion()
    {
        // Arrange
        await using var db = CreateDbContext();
        var definitionId = Guid.NewGuid();
        var existing = new WorkflowDefinition
        {
            Id = definitionId,
            Name = "Publishable Workflow",
            Code = "publishable",
            Status = DefinitionStatus.Draft,
            Version = 1
        };
        db.WorkflowDefinitions.Add(existing);
        await db.SaveChangesAsync();

        var handler = new PublishWorkflowDefinitionCommandHandler(db);
        var command = new PublishWorkflowDefinitionCommand(Id: definitionId);

        // Act
        await handler.Handle(command, CancellationToken.None);

        // Assert
        var entity = await db.WorkflowDefinitions.FindAsync(definitionId);
        entity.Should().NotBeNull();
        entity!.Status.Should().Be(DefinitionStatus.Published);
        entity.Version.Should().Be(2);
    }

    #endregion

    #region GetWorkflowDefinitionList

    [Fact]
    public async Task GetDefinitionList_ReturnsPaged()
    {
        // Arrange
        await using var db = CreateDbContext();
        for (int i = 1; i <= 15; i++)
        {
            db.WorkflowDefinitions.Add(new WorkflowDefinition
            {
                Id = Guid.NewGuid(),
                Name = $"Workflow {i}",
                Code = $"workflow-{i}",
                Status = DefinitionStatus.Draft,
                Version = 1
            });
        }
        await db.SaveChangesAsync();

        var handler = new GetWorkflowDefinitionListQueryHandler(db);
        var query = new GetWorkflowDefinitionListQuery(
            PageIndex: 1,
            PageSize: 10,
            Status: null
        );

        // Act
        var result = await handler.Handle(query, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Items.Should().HaveCount(10);
        result.Total.Should().Be(15);
        result.PageIndex.Should().Be(1);
        result.PageSize.Should().Be(10);
    }

    [Fact]
    public async Task GetDefinitionList_FiltersByStatus()
    {
        // Arrange
        await using var db = CreateDbContext();
        db.WorkflowDefinitions.Add(new WorkflowDefinition
        {
            Id = Guid.NewGuid(),
            Name = "Draft Workflow",
            Code = "draft-wf",
            Status = DefinitionStatus.Draft,
            Version = 1
        });
        db.WorkflowDefinitions.Add(new WorkflowDefinition
        {
            Id = Guid.NewGuid(),
            Name = "Published Workflow",
            Code = "published-wf",
            Status = DefinitionStatus.Published,
            Version = 2
        });
        await db.SaveChangesAsync();

        var handler = new GetWorkflowDefinitionListQueryHandler(db);
        var query = new GetWorkflowDefinitionListQuery(
            PageIndex: 1,
            PageSize: 10,
            Status: DefinitionStatus.Published
        );

        // Act
        var result = await handler.Handle(query, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Items.Should().HaveCount(1);
        result.Items[0].Status.Should().Be(DefinitionStatus.Published);
        result.Total.Should().Be(1);
    }

    #endregion

    #region GetWorkflowDefinitionById

    [Fact]
    public async Task GetDefinitionById_IncludesNodesAndEdges()
    {
        // Arrange
        await using var db = CreateDbContext();
        var definitionId = Guid.NewGuid();

        var definition = new WorkflowDefinition
        {
            Id = definitionId,
            Name = "Workflow With Graph",
            Code = "graph-wf",
            Status = DefinitionStatus.Published,
            Version = 1
        };
        db.WorkflowDefinitions.Add(definition);

        var startNode = new WorkflowNode
        {
            Id = Guid.NewGuid(),
            DefinitionId = definitionId,
            NodeType = NodeType.Start,
            Name = "Start",
            PositionX = 100,
            PositionY = 100
        };
        var endNode = new WorkflowNode
        {
            Id = Guid.NewGuid(),
            DefinitionId = definitionId,
            NodeType = NodeType.End,
            Name = "End",
            PositionX = 500,
            PositionY = 100
        };
        db.WorkflowNodes.AddRange(startNode, endNode);

        var edge = new WorkflowEdge
        {
            Id = Guid.NewGuid(),
            DefinitionId = definitionId,
            SourceNodeId = startNode.Id,
            TargetNodeId = endNode.Id,
            Label = "to end"
        };
        db.WorkflowEdges.Add(edge);
        await db.SaveChangesAsync();

        var handler = new GetWorkflowDefinitionByIdQueryHandler(db);
        var query = new GetWorkflowDefinitionByIdQuery(Id: definitionId);

        // Act
        var result = await handler.Handle(query, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Id.Should().Be(definitionId);
        result.Nodes.Should().NotBeNull().And.HaveCount(2);
        result.Edges.Should().NotBeNull().And.HaveCount(1);

        result.Nodes.Should().ContainSingle(n => n.NodeType == NodeType.Start);
        result.Nodes.Should().ContainSingle(n => n.NodeType == NodeType.End);
        result.Edges[0].SourceNodeId.Should().Be(startNode.Id);
        result.Edges[0].TargetNodeId.Should().Be(endNode.Id);
    }

    #endregion
}
