using ColaFlow.Shared.Kernel.Common;
using ColaFlow.Modules.Identity.Domain.Aggregates.Tenants;
using ColaFlow.Modules.Identity.Domain.Aggregates.Users;
using ColaFlow.Modules.Identity.Domain.Aggregates.Invitations.Events;

namespace ColaFlow.Modules.Identity.Domain.Aggregates.Invitations;

/// <summary>
/// Invitation aggregate root - represents a user invitation to a tenant
/// </summary>
public sealed class Invitation : AggregateRoot
{
    // Identity
    public new InvitationId Id { get; private set; } = null!;

    // Association
    public TenantId TenantId { get; private set; } = null!;

    // Invitation details
    public string Email { get; private set; } = string.Empty;
    public TenantRole Role { get; private set; }

    // Security
    public string TokenHash { get; private set; } = string.Empty;

    // Lifecycle
    public DateTime ExpiresAt { get; private set; }
    public DateTime? AcceptedAt { get; private set; }
    public UserId InvitedBy { get; private set; } = null!;

    // Timestamps
    public DateTime CreatedAt { get; private set; }
    public DateTime UpdatedAt { get; private set; }

    // Status properties
    public bool IsExpired => DateTime.UtcNow > ExpiresAt;
    public bool IsAccepted => AcceptedAt.HasValue;
    public bool IsPending => !IsAccepted && !IsExpired;

    // Private constructor for EF Core
    private Invitation() : base()
    {
    }

    /// <summary>
    /// Creates a new invitation
    /// </summary>
    public static Invitation Create(
        TenantId tenantId,
        string email,
        TenantRole role,
        string tokenHash,
        UserId invitedBy)
    {
        // Validate role - cannot invite as TenantOwner or AIAgent
        if (role == TenantRole.TenantOwner || role == TenantRole.AIAgent)
            throw new InvalidOperationException($"Cannot invite users with role {role}");

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

        if (string.IsNullOrWhiteSpace(tokenHash))
            throw new ArgumentException("Token hash cannot be empty", nameof(tokenHash));

        var invitation = new Invitation
        {
            Id = InvitationId.CreateUnique(),
            TenantId = tenantId,
            Email = email.ToLowerInvariant(),
            Role = role,
            TokenHash = tokenHash,
            ExpiresAt = DateTime.UtcNow.AddDays(7), // 7-day expiration
            InvitedBy = invitedBy,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };

        invitation.AddDomainEvent(new UserInvitedEvent(
            invitation.Id,
            tenantId,
            email,
            role,
            invitedBy));

        return invitation;
    }

    /// <summary>
    /// Accepts the invitation
    /// </summary>
    public void Accept()
    {
        if (!IsPending)
        {
            if (IsExpired)
                throw new InvalidOperationException("Invitation has expired");
            if (IsAccepted)
                throw new InvalidOperationException("Invitation has already been accepted");

            throw new InvalidOperationException("Invitation is not in a valid state to be accepted");
        }

        AcceptedAt = DateTime.UtcNow;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new InvitationAcceptedEvent(
            Id,
            TenantId,
            Email,
            Role));
    }

    /// <summary>
    /// Cancels the invitation (by setting expiration to now)
    /// </summary>
    public void Cancel()
    {
        if (!IsPending)
            throw new InvalidOperationException("Cannot cancel non-pending invitation");

        // Mark as expired to cancel
        ExpiresAt = DateTime.UtcNow;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new InvitationCancelledEvent(Id, TenantId, Email));
    }

    /// <summary>
    /// Validates the invitation state before acceptance
    /// </summary>
    public void ValidateForAcceptance()
    {
        if (IsExpired)
            throw new InvalidOperationException("Invitation has expired");

        if (IsAccepted)
            throw new InvalidOperationException("Invitation has already been accepted");
    }
}
