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

namespace ColaFlow.Modules.Mcp.Domain.Entities;

/// <summary>
/// PendingChange aggregate root - represents a change proposed by an AI agent
/// that requires human approval before being applied to the system
/// </summary>
public sealed class PendingChange : AggregateRoot
{
    // Multi-tenant isolation
    public Guid TenantId { get; private set; }

    // API Key that created this change
    public Guid ApiKeyId { get; private set; }

    // MCP Tool information
    public string ToolName { get; private set; } = null!;

    // The diff preview containing the proposed changes
    public DiffPreview Diff { get; private set; } = null!;

    // Status and lifecycle
    public PendingChangeStatus Status { get; private set; }
    public DateTime CreatedAt { get; private set; }
    public DateTime ExpiresAt { get; private set; }

    // Approval tracking
    public Guid? ApprovedBy { get; private set; }
    public DateTime? ApprovedAt { get; private set; }

    // Rejection tracking
    public Guid? RejectedBy { get; private set; }
    public DateTime? RejectedAt { get; private set; }
    public string? RejectionReason { get; private set; }

    // Application tracking
    public DateTime? AppliedAt { get; private set; }
    public string? ApplicationResult { get; private set; }

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

    /// <summary>
    /// Factory method to create a new pending change
    /// </summary>
    /// <param name="toolName">The MCP tool name that created this change</param>
    /// <param name="diff">The diff preview showing the proposed changes</param>
    /// <param name="tenantId">Tenant ID for multi-tenant isolation</param>
    /// <param name="apiKeyId">API Key ID that authorized this change</param>
    /// <param name="expirationHours">Hours until the change expires (default: 24)</param>
    /// <returns>A new PendingChange entity</returns>
    public static PendingChange Create(
        string toolName,
        DiffPreview diff,
        Guid tenantId,
        Guid apiKeyId,
        int expirationHours = 24)
    {
        // Validation
        if (string.IsNullOrWhiteSpace(toolName))
            throw new ArgumentException("Tool name cannot be empty", nameof(toolName));

        if (diff == null)
            throw new ArgumentNullException(nameof(diff));

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

        if (apiKeyId == Guid.Empty)
            throw new ArgumentException("API Key ID cannot be empty", nameof(apiKeyId));

        if (expirationHours <= 0 || expirationHours > 168) // Max 7 days
            throw new ArgumentException(
                "Expiration hours must be between 1 and 168 (7 days)",
                nameof(expirationHours));

        var pendingChange = new PendingChange
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            ApiKeyId = apiKeyId,
            ToolName = toolName,
            Diff = diff,
            Status = PendingChangeStatus.PendingApproval,
            CreatedAt = DateTime.UtcNow,
            ExpiresAt = DateTime.UtcNow.AddHours(expirationHours)
        };

        // Raise domain event
        pendingChange.AddDomainEvent(new PendingChangeCreatedEvent(
            pendingChange.Id,
            toolName,
            diff.EntityType,
            diff.Operation,
            tenantId
        ));

        return pendingChange;
    }

    /// <summary>
    /// Approve the pending change
    /// </summary>
    /// <param name="approvedBy">User ID who approved the change</param>
    public void Approve(Guid approvedBy)
    {
        // Business rule: Can only approve changes that are pending
        if (Status != PendingChangeStatus.PendingApproval)
            throw new InvalidOperationException(
                $"Cannot approve change with status {Status}. Only PendingApproval changes can be approved.");

        // Business rule: Cannot approve expired changes
        if (IsExpired())
            throw new InvalidOperationException(
                "Cannot approve an expired change. The change has exceeded its expiration time.");

        if (approvedBy == Guid.Empty)
            throw new ArgumentException("Approved by user ID cannot be empty", nameof(approvedBy));

        Status = PendingChangeStatus.Approved;
        ApprovedBy = approvedBy;
        ApprovedAt = DateTime.UtcNow;

        // Raise domain event
        AddDomainEvent(new PendingChangeApprovedEvent(
            Id,
            ToolName,
            Diff,
            approvedBy,
            TenantId
        ));
    }

    /// <summary>
    /// Reject the pending change
    /// </summary>
    /// <param name="rejectedBy">User ID who rejected the change</param>
    /// <param name="reason">Reason for rejection</param>
    public void Reject(Guid rejectedBy, string reason)
    {
        // Business rule: Can only reject changes that are pending
        if (Status != PendingChangeStatus.PendingApproval)
            throw new InvalidOperationException(
                $"Cannot reject change with status {Status}. Only PendingApproval changes can be rejected.");

        if (rejectedBy == Guid.Empty)
            throw new ArgumentException("Rejected by user ID cannot be empty", nameof(rejectedBy));

        if (string.IsNullOrWhiteSpace(reason))
            throw new ArgumentException("Rejection reason cannot be empty", nameof(reason));

        Status = PendingChangeStatus.Rejected;
        RejectedBy = rejectedBy;
        RejectedAt = DateTime.UtcNow;
        RejectionReason = reason;

        // Raise domain event
        AddDomainEvent(new PendingChangeRejectedEvent(
            Id,
            ToolName,
            reason,
            rejectedBy,
            TenantId
        ));
    }

    /// <summary>
    /// Mark the change as expired
    /// This is typically called by a background job that checks for expired changes
    /// </summary>
    public void Expire()
    {
        // Business rule: Can only expire changes that are pending
        if (Status != PendingChangeStatus.PendingApproval)
            return; // Already processed, nothing to do

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

        Status = PendingChangeStatus.Expired;

        // Raise domain event
        AddDomainEvent(new PendingChangeExpiredEvent(
            Id,
            ToolName,
            TenantId
        ));
    }

    /// <summary>
    /// Mark the change as applied after successful execution
    /// </summary>
    /// <param name="result">Description of the application result</param>
    public void MarkAsApplied(string result)
    {
        // Business rule: Can only apply approved changes
        if (Status != PendingChangeStatus.Approved)
            throw new InvalidOperationException(
                $"Cannot apply change with status {Status}. Only Approved changes can be applied.");

        if (string.IsNullOrWhiteSpace(result))
            throw new ArgumentException("Application result cannot be empty", nameof(result));

        Status = PendingChangeStatus.Applied;
        AppliedAt = DateTime.UtcNow;
        ApplicationResult = result;

        // Raise domain event
        AddDomainEvent(new PendingChangeAppliedEvent(
            Id,
            ToolName,
            Diff.EntityType,
            Diff.EntityId,
            result,
            TenantId
        ));
    }

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

    /// <summary>
    /// Check if the change can be approved
    /// </summary>
    public bool CanBeApproved()
    {
        return Status == PendingChangeStatus.PendingApproval && !IsExpired();
    }

    /// <summary>
    /// Check if the change can be rejected
    /// </summary>
    public bool CanBeRejected()
    {
        return Status == PendingChangeStatus.PendingApproval;
    }

    /// <summary>
    /// Get a human-readable summary of the change
    /// </summary>
    public string GetSummary()
    {
        return $"{ToolName}: {Diff.GetSummary()} - {Status}";
    }
}
