using ColaFlow.Shared.Kernel.Common;
using ColaFlow.Modules.Mcp.Domain.Events;
using ColaFlow.Modules.Mcp.Domain.ValueObjects;

namespace ColaFlow.Modules.Mcp.Domain.Entities;

/// <summary>
/// TaskLock aggregate root - prevents concurrent modifications to the same resource
/// Used to ensure AI agents don't conflict when making changes
/// </summary>
public sealed class TaskLock : AggregateRoot
{
    // Multi-tenant isolation
    public Guid TenantId { get; private set; }

    // Resource being locked
    public string ResourceType { get; private set; } = null!;
    public Guid ResourceId { get; private set; }

    // Lock holder information
    public string LockHolderType { get; private set; } = null!; // "AI_AGENT" or "USER"
    public Guid LockHolderId { get; private set; } // ApiKeyId for AI agents, UserId for users
    public string? LockHolderName { get; private set; } // Friendly name for display

    // Lock lifecycle
    public TaskLockStatus Status { get; private set; }
    public DateTime AcquiredAt { get; private set; }
    public DateTime ExpiresAt { get; private set; }
    public DateTime? ReleasedAt { get; private set; }

    // Additional context
    public string? Purpose { get; private set; } // Optional: why is the lock held?

    /// <summary>
    /// Private constructor for EF Core
    /// </summary>
    private TaskLock() : base()
    {
    }

    /// <summary>
    /// Factory method to acquire a new task lock
    /// </summary>
    /// <param name="resourceType">Type of resource being locked (e.g., "Epic", "Story", "Task")</param>
    /// <param name="resourceId">ID of the specific resource</param>
    /// <param name="lockHolderType">Type of lock holder: AI_AGENT or USER</param>
    /// <param name="lockHolderId">ID of the lock holder (ApiKeyId or UserId)</param>
    /// <param name="tenantId">Tenant ID for multi-tenant isolation</param>
    /// <param name="lockHolderName">Friendly name of the lock holder</param>
    /// <param name="purpose">Optional purpose description</param>
    /// <param name="expirationMinutes">Minutes until lock expires (default: 5)</param>
    /// <returns>A new TaskLock entity</returns>
    public static TaskLock Acquire(
        string resourceType,
        Guid resourceId,
        string lockHolderType,
        Guid lockHolderId,
        Guid tenantId,
        string? lockHolderName = null,
        string? purpose = null,
        int expirationMinutes = 5)
    {
        // Validation
        if (string.IsNullOrWhiteSpace(resourceType))
            throw new ArgumentException("Resource type cannot be empty", nameof(resourceType));

        if (resourceId == Guid.Empty)
            throw new ArgumentException("Resource ID cannot be empty", nameof(resourceId));

        if (string.IsNullOrWhiteSpace(lockHolderType))
            throw new ArgumentException("Lock holder type cannot be empty", nameof(lockHolderType));

        lockHolderType = lockHolderType.ToUpperInvariant();
        if (lockHolderType != "AI_AGENT" && lockHolderType != "USER")
            throw new ArgumentException(
                "Lock holder type must be AI_AGENT or USER",
                nameof(lockHolderType));

        if (lockHolderId == Guid.Empty)
            throw new ArgumentException("Lock holder ID cannot be empty", nameof(lockHolderId));

        if (tenantId == Guid.Empty)
            throw new ArgumentException("Tenant ID cannot be empty", nameof(tenantId));

        if (expirationMinutes <= 0 || expirationMinutes > 60) // Max 1 hour
            throw new ArgumentException(
                "Expiration minutes must be between 1 and 60",
                nameof(expirationMinutes));

        var taskLock = new TaskLock
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            ResourceType = resourceType,
            ResourceId = resourceId,
            LockHolderType = lockHolderType,
            LockHolderId = lockHolderId,
            LockHolderName = lockHolderName,
            Purpose = purpose,
            Status = TaskLockStatus.Active,
            AcquiredAt = DateTime.UtcNow,
            ExpiresAt = DateTime.UtcNow.AddMinutes(expirationMinutes)
        };

        // Raise domain event
        taskLock.AddDomainEvent(new TaskLockAcquiredEvent(
            taskLock.Id,
            resourceType,
            resourceId,
            lockHolderType,
            lockHolderId,
            tenantId
        ));

        return taskLock;
    }

    /// <summary>
    /// Release the lock explicitly
    /// </summary>
    public void Release()
    {
        // Business rule: Can only release active locks
        if (Status != TaskLockStatus.Active)
            throw new InvalidOperationException(
                $"Cannot release lock with status {Status}. Only Active locks can be released.");

        Status = TaskLockStatus.Released;
        ReleasedAt = DateTime.UtcNow;

        // Raise domain event
        AddDomainEvent(new TaskLockReleasedEvent(
            Id,
            ResourceType,
            ResourceId,
            LockHolderType,
            LockHolderId,
            TenantId
        ));
    }

    /// <summary>
    /// Mark the lock as expired
    /// This is typically called by a background job or when checking lock validity
    /// </summary>
    public void MarkAsExpired()
    {
        // Business rule: Can only expire active locks
        if (Status != TaskLockStatus.Active)
            return; // Already processed, nothing to do

        // Business rule: Cannot expire before expiration time
        if (!IsExpired())
            throw new InvalidOperationException(
                "Cannot mark lock as expired before its expiration time. " +
                $"Expiration time: {ExpiresAt:yyyy-MM-dd HH:mm:ss} UTC");

        Status = TaskLockStatus.Expired;

        // Raise domain event
        AddDomainEvent(new TaskLockExpiredEvent(
            Id,
            ResourceType,
            ResourceId,
            LockHolderId,
            TenantId
        ));
    }

    /// <summary>
    /// Extend the lock expiration time
    /// Useful when an operation is taking longer than expected
    /// </summary>
    /// <param name="additionalMinutes">Additional minutes to add to expiration (max 60)</param>
    public void ExtendExpiration(int additionalMinutes)
    {
        // Business rule: Can only extend active locks
        if (Status != TaskLockStatus.Active)
            throw new InvalidOperationException(
                $"Cannot extend lock with status {Status}. Only Active locks can be extended.");

        if (additionalMinutes <= 0 || additionalMinutes > 60)
            throw new ArgumentException(
                "Additional minutes must be between 1 and 60",
                nameof(additionalMinutes));

        // Don't allow extending beyond 2 hours from acquisition
        var maxExpiration = AcquiredAt.AddHours(2);
        var newExpiration = ExpiresAt.AddMinutes(additionalMinutes);

        if (newExpiration > maxExpiration)
            throw new InvalidOperationException(
                "Cannot extend lock beyond 2 hours from acquisition time. " +
                "Please release and re-acquire if needed.");

        ExpiresAt = newExpiration;
    }

    /// <summary>
    /// Check if the lock has expired
    /// </summary>
    public bool IsExpired()
    {
        return DateTime.UtcNow > ExpiresAt;
    }

    /// <summary>
    /// Check if the lock is currently valid (active and not expired)
    /// </summary>
    public bool IsValid()
    {
        return Status == TaskLockStatus.Active && !IsExpired();
    }

    /// <summary>
    /// Check if the lock is held by the specified holder
    /// </summary>
    public bool IsHeldBy(Guid holderId)
    {
        return LockHolderId == holderId && IsValid();
    }

    /// <summary>
    /// Check if the lock is held by an AI agent
    /// </summary>
    public bool IsHeldByAiAgent()
    {
        return LockHolderType == "AI_AGENT" && IsValid();
    }

    /// <summary>
    /// Check if the lock is held by a user
    /// </summary>
    public bool IsHeldByUser()
    {
        return LockHolderType == "USER" && IsValid();
    }

    /// <summary>
    /// Get remaining time before lock expiration
    /// </summary>
    public TimeSpan GetRemainingTime()
    {
        if (!IsValid())
            return TimeSpan.Zero;

        var remaining = ExpiresAt - DateTime.UtcNow;
        return remaining > TimeSpan.Zero ? remaining : TimeSpan.Zero;
    }

    /// <summary>
    /// Get a human-readable summary of the lock
    /// </summary>
    public string GetSummary()
    {
        var holderName = LockHolderName ?? LockHolderId.ToString();
        var remaining = GetRemainingTime();
        return $"{ResourceType} {ResourceId} locked by {holderName} ({LockHolderType}) - " +
               $"{Status} - Remaining: {remaining.TotalMinutes:F1}m";
    }
}
