using ColaFlow.Modules.Mcp.Domain.Entities;
using ColaFlow.Modules.Mcp.Domain.Events;
using ColaFlow.Modules.Mcp.Domain.ValueObjects;
using Xunit;

namespace ColaFlow.Modules.Mcp.Tests.Domain;

public class TaskLockTests
{
    [Fact]
    public void Acquire_ValidInput_Success()
    {
        // Arrange
        var resourceType = "Epic";
        var resourceId = Guid.NewGuid();
        var lockHolderId = Guid.NewGuid();
        var tenantId = Guid.NewGuid();

        // Act
        var taskLock = TaskLock.Acquire(
            resourceType: resourceType,
            resourceId: resourceId,
            lockHolderType: "AI_AGENT",
            lockHolderId: lockHolderId,
            tenantId: tenantId,
            lockHolderName: "Claude",
            purpose: "Creating new epic"
        );

        // Assert
        Assert.NotEqual(Guid.Empty, taskLock.Id);
        Assert.Equal(resourceType, taskLock.ResourceType);
        Assert.Equal(resourceId, taskLock.ResourceId);
        Assert.Equal("AI_AGENT", taskLock.LockHolderType);
        Assert.Equal(lockHolderId, taskLock.LockHolderId);
        Assert.Equal(tenantId, taskLock.TenantId);
        Assert.Equal("Claude", taskLock.LockHolderName);
        Assert.Equal("Creating new epic", taskLock.Purpose);
        Assert.Equal(TaskLockStatus.Active, taskLock.Status);
        Assert.True(taskLock.ExpiresAt > DateTime.UtcNow);
        Assert.Null(taskLock.ReleasedAt);
    }

    [Fact]
    public void Acquire_RaisesTaskLockAcquiredEvent()
    {
        // Arrange & Act
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid()
        );

        // Assert
        Assert.Single(taskLock.DomainEvents);
        var domainEvent = taskLock.DomainEvents.First();
        Assert.IsType<TaskLockAcquiredEvent>(domainEvent);
        var acquiredEvent = (TaskLockAcquiredEvent)domainEvent;
        Assert.Equal(taskLock.Id, acquiredEvent.LockId);
        Assert.Equal("Epic", acquiredEvent.ResourceType);
    }

    [Fact]
    public void Acquire_EmptyResourceType_ThrowsException()
    {
        // Act & Assert
        Assert.Throws<ArgumentException>(() => TaskLock.Acquire(
            resourceType: "",
            resourceId: Guid.NewGuid(),
            lockHolderType: "AI_AGENT",
            lockHolderId: Guid.NewGuid(),
            tenantId: Guid.NewGuid()
        ));
    }

    [Fact]
    public void Acquire_InvalidLockHolderType_ThrowsException()
    {
        // Act & Assert
        Assert.Throws<ArgumentException>(() => TaskLock.Acquire(
            resourceType: "Epic",
            resourceId: Guid.NewGuid(),
            lockHolderType: "INVALID",
            lockHolderId: Guid.NewGuid(),
            tenantId: Guid.NewGuid()
        ));
    }

    [Fact]
    public void Acquire_LowercaseLockHolderType_NormalizedToUppercase()
    {
        // Act
        var taskLock = TaskLock.Acquire(
            resourceType: "Epic",
            resourceId: Guid.NewGuid(),
            lockHolderType: "ai_agent",
            lockHolderId: Guid.NewGuid(),
            tenantId: Guid.NewGuid()
        );

        // Assert
        Assert.Equal("AI_AGENT", taskLock.LockHolderType);
    }

    [Fact]
    public void Acquire_CustomExpirationMinutes_SetsCorrectExpiration()
    {
        // Arrange
        var before = DateTime.UtcNow;

        // Act
        var taskLock = TaskLock.Acquire(
            resourceType: "Epic",
            resourceId: Guid.NewGuid(),
            lockHolderType: "AI_AGENT",
            lockHolderId: Guid.NewGuid(),
            tenantId: Guid.NewGuid(),
            expirationMinutes: 10
        );

        // Assert
        var after = DateTime.UtcNow;
        Assert.True(taskLock.ExpiresAt >= before.AddMinutes(10));
        Assert.True(taskLock.ExpiresAt <= after.AddMinutes(10));
    }

    [Fact]
    public void Release_ActiveLock_Success()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid()
        );
        taskLock.ClearDomainEvents();

        // Act
        taskLock.Release();

        // Assert
        Assert.Equal(TaskLockStatus.Released, taskLock.Status);
        Assert.NotNull(taskLock.ReleasedAt);
        Assert.True(taskLock.ReleasedAt <= DateTime.UtcNow);
    }

    [Fact]
    public void Release_RaisesTaskLockReleasedEvent()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid()
        );
        taskLock.ClearDomainEvents();

        // Act
        taskLock.Release();

        // Assert
        Assert.Single(taskLock.DomainEvents);
        Assert.IsType<TaskLockReleasedEvent>(taskLock.DomainEvents.First());
    }

    [Fact]
    public void Release_AlreadyReleased_ThrowsException()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid()
        );
        taskLock.Release();

        // Act & Assert
        Assert.Throws<InvalidOperationException>(() => taskLock.Release());
    }

    [Fact]
    public void MarkAsExpired_ExpiredActiveLock_Success()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 5
        );

        // Use reflection to set ExpiresAt to the past
        var expiresAtProperty = typeof(TaskLock).GetProperty("ExpiresAt");
        expiresAtProperty!.SetValue(taskLock, DateTime.UtcNow.AddMinutes(-1));

        taskLock.ClearDomainEvents();

        // Act
        taskLock.MarkAsExpired();

        // Assert
        Assert.Equal(TaskLockStatus.Expired, taskLock.Status);
    }

    [Fact]
    public void MarkAsExpired_RaisesTaskLockExpiredEvent()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 5
        );

        // Use reflection to set ExpiresAt to the past
        var expiresAtProperty = typeof(TaskLock).GetProperty("ExpiresAt");
        expiresAtProperty!.SetValue(taskLock, DateTime.UtcNow.AddMinutes(-1));

        taskLock.ClearDomainEvents();

        // Act
        taskLock.MarkAsExpired();

        // Assert
        Assert.Single(taskLock.DomainEvents);
        Assert.IsType<TaskLockExpiredEvent>(taskLock.DomainEvents.First());
    }

    [Fact]
    public void MarkAsExpired_NotYetExpired_ThrowsException()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 5
        );

        // Act & Assert
        Assert.Throws<InvalidOperationException>(() => taskLock.MarkAsExpired());
    }

    [Fact]
    public void MarkAsExpired_AlreadyReleased_DoesNothing()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 5
        );

        // Use reflection to set ExpiresAt to the past
        var expiresAtProperty = typeof(TaskLock).GetProperty("ExpiresAt");
        expiresAtProperty!.SetValue(taskLock, DateTime.UtcNow.AddMinutes(-1));

        taskLock.Release();
        var statusBefore = taskLock.Status;
        taskLock.ClearDomainEvents();

        // Act
        taskLock.MarkAsExpired();

        // Assert
        Assert.Equal(statusBefore, taskLock.Status);
        Assert.Empty(taskLock.DomainEvents);
    }

    [Fact]
    public void ExtendExpiration_ActiveLock_Success()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 5
        );
        var expirationBefore = taskLock.ExpiresAt;

        // Act
        taskLock.ExtendExpiration(10);

        // Assert
        Assert.True(taskLock.ExpiresAt > expirationBefore);
        Assert.True(taskLock.ExpiresAt >= expirationBefore.AddMinutes(10));
    }

    [Fact]
    public void ExtendExpiration_ReleasedLock_ThrowsException()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid()
        );
        taskLock.Release();

        // Act & Assert
        Assert.Throws<InvalidOperationException>(() => taskLock.ExtendExpiration(10));
    }

    [Fact]
    public void ExtendExpiration_BeyondMaxDuration_ThrowsException()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 60
        );

        // Use reflection to set AcquiredAt to 1.5 hours ago
        // Current expiration would be at 30 minutes from now (1.5 hours + 60 minutes = 2.5 hours, then - 30min = 2 hours)
        // Extending by more than 1 minute will exceed the 2-hour max
        var acquiredAtProperty = typeof(TaskLock).GetProperty("AcquiredAt");
        var expiresAtProperty = typeof(TaskLock).GetProperty("ExpiresAt");

        var pastTime = DateTime.UtcNow.AddHours(-1.5);
        acquiredAtProperty!.SetValue(taskLock, pastTime);
        // Set expires to 1 hour 55 minutes from now (2 hours total - 5 minutes buffer)
        expiresAtProperty!.SetValue(taskLock, DateTime.UtcNow.AddMinutes(115));

        // Try to extend by 10 minutes - this would push expiration to 2 hours 5 minutes total, exceeding limit
        // Act & Assert
        Assert.Throws<InvalidOperationException>(() => taskLock.ExtendExpiration(10));
    }

    [Fact]
    public void IsExpired_ExpiredLock_ReturnsTrue()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 5
        );

        // Use reflection to set ExpiresAt to the past
        var expiresAtProperty = typeof(TaskLock).GetProperty("ExpiresAt");
        expiresAtProperty!.SetValue(taskLock, DateTime.UtcNow.AddMinutes(-1));

        // Act & Assert
        Assert.True(taskLock.IsExpired());
    }

    [Fact]
    public void IsExpired_NotExpiredLock_ReturnsFalse()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 5
        );

        // Act & Assert
        Assert.False(taskLock.IsExpired());
    }

    [Fact]
    public void IsValid_ActiveNotExpired_ReturnsTrue()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 5
        );

        // Act & Assert
        Assert.True(taskLock.IsValid());
    }

    [Fact]
    public void IsValid_Released_ReturnsFalse()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid()
        );
        taskLock.Release();

        // Act & Assert
        Assert.False(taskLock.IsValid());
    }

    [Fact]
    public void IsValid_Expired_ReturnsFalse()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 5
        );

        // Use reflection to set ExpiresAt to the past
        var expiresAtProperty = typeof(TaskLock).GetProperty("ExpiresAt");
        expiresAtProperty!.SetValue(taskLock, DateTime.UtcNow.AddMinutes(-1));

        // Act & Assert
        Assert.False(taskLock.IsValid());
    }

    [Fact]
    public void IsHeldBy_CorrectHolder_ReturnsTrue()
    {
        // Arrange
        var holderId = Guid.NewGuid();
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            holderId,
            Guid.NewGuid()
        );

        // Act & Assert
        Assert.True(taskLock.IsHeldBy(holderId));
    }

    [Fact]
    public void IsHeldBy_DifferentHolder_ReturnsFalse()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid()
        );

        // Act & Assert
        Assert.False(taskLock.IsHeldBy(Guid.NewGuid()));
    }

    [Fact]
    public void IsHeldByAiAgent_AiAgentLock_ReturnsTrue()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid()
        );

        // Act & Assert
        Assert.True(taskLock.IsHeldByAiAgent());
    }

    [Fact]
    public void IsHeldByAiAgent_UserLock_ReturnsFalse()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "USER",
            Guid.NewGuid(),
            Guid.NewGuid()
        );

        // Act & Assert
        Assert.False(taskLock.IsHeldByAiAgent());
    }

    [Fact]
    public void IsHeldByUser_UserLock_ReturnsTrue()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "USER",
            Guid.NewGuid(),
            Guid.NewGuid()
        );

        // Act & Assert
        Assert.True(taskLock.IsHeldByUser());
    }

    [Fact]
    public void IsHeldByUser_AiAgentLock_ReturnsFalse()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid()
        );

        // Act & Assert
        Assert.False(taskLock.IsHeldByUser());
    }

    [Fact]
    public void GetRemainingTime_ActiveLock_ReturnsPositiveTimeSpan()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 5
        );

        // Act
        var remaining = taskLock.GetRemainingTime();

        // Assert
        Assert.True(remaining.TotalMinutes > 0);
        Assert.True(remaining.TotalMinutes <= 5);
    }

    [Fact]
    public void GetRemainingTime_ExpiredLock_ReturnsZero()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            "Epic",
            Guid.NewGuid(),
            "AI_AGENT",
            Guid.NewGuid(),
            Guid.NewGuid(),
            expirationMinutes: 5
        );

        // Use reflection to set ExpiresAt to the past
        var expiresAtProperty = typeof(TaskLock).GetProperty("ExpiresAt");
        expiresAtProperty!.SetValue(taskLock, DateTime.UtcNow.AddMinutes(-1));

        // Act
        var remaining = taskLock.GetRemainingTime();

        // Assert
        Assert.Equal(TimeSpan.Zero, remaining);
    }

    [Fact]
    public void GetSummary_ReturnsFormattedString()
    {
        // Arrange
        var taskLock = TaskLock.Acquire(
            resourceType: "Epic",
            resourceId: Guid.NewGuid(),
            lockHolderType: "AI_AGENT",
            lockHolderId: Guid.NewGuid(),
            tenantId: Guid.NewGuid(),
            lockHolderName: "Claude"
        );

        // Act
        var summary = taskLock.GetSummary();

        // Assert
        Assert.Contains("Epic", summary);
        Assert.Contains("Claude", summary);
        Assert.Contains("AI_AGENT", summary);
        Assert.Contains("Active", summary);
    }
}
