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

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

/// <summary>
/// User aggregate root - multi-tenant aware with SSO support
/// </summary>
public sealed class User : AggregateRoot
{
    // Tenant association
    public TenantId TenantId { get; private set; } = null!;

    // User identity
    public Email Email { get; private set; } = null!;
    public string PasswordHash { get; private set; } = string.Empty;
    public FullName FullName { get; private set; } = null!;
    public UserStatus Status { get; private set; }

    // SSO properties
    public AuthenticationProvider AuthProvider { get; private set; }
    public string? ExternalUserId { get; private set; } // IdP user ID
    public string? ExternalEmail { get; private set; }  // Email from IdP

    // Profile
    public string? AvatarUrl { get; private set; }
    public string? JobTitle { get; private set; }
    public string? PhoneNumber { get; private set; }

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

    // Email verification status
    public bool IsEmailVerified => EmailVerifiedAt.HasValue;

    // Security (deprecated - moved to separate token entities)
    public string? EmailVerificationToken { get; private set; }
    public string? PasswordResetToken { get; private set; }
    public DateTime? PasswordResetTokenExpiresAt { get; private set; }

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

    // Factory method for local authentication
    public static User CreateLocal(
        TenantId tenantId,
        Email email,
        string passwordHash,
        FullName fullName)
    {
        var user = new User
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            Email = email,
            PasswordHash = passwordHash,
            FullName = fullName,
            Status = UserStatus.Active,
            AuthProvider = AuthenticationProvider.Local,
            CreatedAt = DateTime.UtcNow
        };

        user.AddDomainEvent(new UserCreatedEvent(user.Id, user.Email, tenantId));

        return user;
    }

    // Factory method for SSO authentication
    public static User CreateFromSso(
        TenantId tenantId,
        AuthenticationProvider provider,
        string externalUserId,
        Email email,
        FullName fullName,
        string? avatarUrl = null)
    {
        if (provider == AuthenticationProvider.Local)
            throw new ArgumentException("Use CreateLocal for local authentication");

        var user = new User
        {
            Id = Guid.NewGuid(),
            TenantId = tenantId,
            Email = email,
            PasswordHash = string.Empty, // No password for SSO users
            FullName = fullName,
            Status = UserStatus.Active,
            AuthProvider = provider,
            ExternalUserId = externalUserId,
            ExternalEmail = email,
            AvatarUrl = avatarUrl,
            CreatedAt = DateTime.UtcNow,
            EmailVerifiedAt = DateTime.UtcNow // Trust IdP verification
        };

        user.AddDomainEvent(new UserCreatedFromSsoEvent(user.Id, user.Email, tenantId, provider));

        return user;
    }

    // Business methods
    public void UpdatePassword(string newPasswordHash)
    {
        if (AuthProvider != AuthenticationProvider.Local)
            throw new InvalidOperationException("Cannot change password for SSO users");

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

        PasswordHash = newPasswordHash;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new UserPasswordChangedEvent(Id));
    }

    public void UpdateProfile(FullName? fullName = null, string? avatarUrl = null, string? jobTitle = null, string? phoneNumber = null)
    {
        if (fullName is not null)
            FullName = fullName;

        if (avatarUrl is not null)
            AvatarUrl = avatarUrl;

        if (jobTitle is not null)
            JobTitle = jobTitle;

        if (phoneNumber is not null)
            PhoneNumber = phoneNumber;

        UpdatedAt = DateTime.UtcNow;
    }

    public void RecordLogin()
    {
        LastLoginAt = DateTime.UtcNow;
        UpdatedAt = DateTime.UtcNow;
    }

    public void RecordLoginWithEvent(TenantId tenantId, string ipAddress, string userAgent)
    {
        LastLoginAt = DateTime.UtcNow;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new UserLoggedInEvent(Id, tenantId, ipAddress, userAgent));
    }

    public void RaiseRoleAssignedEvent(TenantId tenantId, TenantRole role, TenantRole? previousRole, Guid assignedBy)
    {
        AddDomainEvent(new UserRoleAssignedEvent(Id, tenantId, role, previousRole, assignedBy));
    }

    public void RaiseRemovedFromTenantEvent(TenantId tenantId, Guid removedBy, string reason)
    {
        AddDomainEvent(new UserRemovedFromTenantEvent(Id, tenantId, removedBy, reason));
    }

    public void VerifyEmail()
    {
        if (IsEmailVerified)
            return; // Already verified, idempotent

        EmailVerifiedAt = DateTime.UtcNow;
        EmailVerificationToken = null;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new EmailVerifiedEvent(Id, Email));
    }

    public void Suspend(string reason)
    {
        if (Status == UserStatus.Deleted)
            throw new InvalidOperationException("Cannot suspend deleted user");

        Status = UserStatus.Suspended;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new UserSuspendedEvent(Id, reason));
    }

    public void Reactivate()
    {
        if (Status == UserStatus.Deleted)
            throw new InvalidOperationException("Cannot reactivate deleted user");

        Status = UserStatus.Active;
        UpdatedAt = DateTime.UtcNow;
    }

    public void Delete()
    {
        Status = UserStatus.Deleted;
        UpdatedAt = DateTime.UtcNow;
    }

    // SSO-specific methods
    public void UpdateSsoProfile(string externalUserId, Email email, FullName fullName, string? avatarUrl = null)
    {
        if (AuthProvider == AuthenticationProvider.Local)
            throw new InvalidOperationException("Cannot update SSO profile for local users");

        ExternalUserId = externalUserId;
        ExternalEmail = email;
        FullName = fullName;

        if (avatarUrl is not null)
            AvatarUrl = avatarUrl;

        UpdatedAt = DateTime.UtcNow;
    }

    public void SetPasswordResetToken(string token, DateTime expiresAt)
    {
        if (AuthProvider != AuthenticationProvider.Local)
            throw new InvalidOperationException("Cannot set password reset token for SSO users");

        PasswordResetToken = token;
        PasswordResetTokenExpiresAt = expiresAt;
        UpdatedAt = DateTime.UtcNow;
    }

    public void ClearPasswordResetToken()
    {
        PasswordResetToken = null;
        PasswordResetTokenExpiresAt = null;
        UpdatedAt = DateTime.UtcNow;
    }

    public void SetEmailVerificationToken(string token)
    {
        EmailVerificationToken = token;
        UpdatedAt = DateTime.UtcNow;
    }
}
