// EN: MerchantStaff aggregate root.
// VI: Aggregate root MerchantStaff.

using System.Security.Cryptography;
using System.Text;
using MerchantService.Domain.AggregatesModel.ShopAggregate;
using MerchantService.Domain.Events;
using MerchantService.Domain.Exceptions;
using MerchantService.Domain.SeedWork;

namespace MerchantService.Domain.AggregatesModel.MerchantStaffAggregate;

/// <summary>
/// EN: MerchantStaff aggregate root - represents an employee of a merchant.
/// VI: Aggregate root MerchantStaff - đại diện cho nhân viên của merchant.
/// </summary>
public class MerchantStaff : Entity, IAggregateRoot
{
    // EN: Private fields for encapsulation
    // VI: Fields private để đóng gói
    private Guid _userId;
    private Guid _merchantId;
    private string? _employeeCode;
    private StaffRole _role = null!;
    private StaffStatus _status = null!;
    private StaffPermissions _permissions;
    private string? _phone;
    private string? _email;
    private string? _pinCodeHash;
    private DateTime _joinedAt;
    private DateTime? _terminatedAt;
    private DateTime _createdAt;
    private DateTime? _updatedAt;
    private string? _firstName;
    private string? _lastName;
    private string? _address;
    private string? _profilePhotoUrl;
    private string? _documentFrontUrl;
    private string? _documentBackUrl;

    private readonly List<DeviceToken> _deviceTokens = new();
    private readonly List<ShopMember> _shopAssignments = new();

    /// <summary>
    /// EN: Reference to IAM User.
    /// VI: Tham chiếu đến IAM User.
    /// </summary>
    public Guid UserId => _userId;

    /// <summary>
    /// EN: Merchant ID (employer).
    /// VI: ID merchant (chủ lao động).
    /// </summary>
    public Guid MerchantId => _merchantId;

    /// <summary>
    /// EN: Employee code for internal reference.
    /// VI: Mã nhân viên để tham chiếu nội bộ.
    /// </summary>
    public string? EmployeeCode => _employeeCode;

    /// <summary>
    /// EN: Staff role.
    /// VI: Vai trò nhân viên.
    /// </summary>
    public StaffRole Role => _role;

    /// <summary>
    /// EN: Role ID for EF Core mapping.
    /// VI: Role ID cho EF Core mapping.
    /// </summary>
    public int RoleId { get; private set; }

    /// <summary>
    /// EN: Staff status.
    /// VI: Trạng thái nhân viên.
    /// </summary>
    public StaffStatus Status => _status;

    /// <summary>
    /// EN: Status ID for EF Core mapping.
    /// VI: Status ID cho EF Core mapping.
    /// </summary>
    public int StatusId { get; private set; }

    /// <summary>
    /// EN: Staff permissions bitmask.
    /// VI: Bitmask quyền hạn nhân viên.
    /// </summary>
    public StaffPermissions Permissions => _permissions;

    /// <summary>
    /// EN: Staff phone number.
    /// VI: Số điện thoại nhân viên.
    /// </summary>
    public string? Phone => _phone;

    /// <summary>
    /// EN: Staff email.
    /// VI: Email nhân viên.
    /// </summary>
    public string? Email => _email;

    /// <summary>
    /// EN: Hashed PIN code for POS authentication.
    /// VI: Mã PIN đã hash cho xác thực POS.
    /// </summary>
    public string? PinCodeHash => _pinCodeHash;

    /// <summary>
    /// EN: Registered devices for this staff.
    /// VI: Các thiết bị đã đăng ký của nhân viên này.
    /// </summary>
    public IReadOnlyCollection<DeviceToken> DeviceTokens => _deviceTokens.AsReadOnly();

    /// <summary>
    /// EN: Shop assignments for this staff.
    /// VI: Các shop được gán cho nhân viên này.
    /// </summary>
    public IReadOnlyCollection<ShopMember> ShopAssignments => _shopAssignments.AsReadOnly();

    /// <summary>
    /// EN: When staff joined.
    /// VI: Thời điểm nhân viên gia nhập.
    /// </summary>
    public DateTime JoinedAt => _joinedAt;

    /// <summary>
    /// EN: When employment was terminated.
    /// VI: Thời điểm chấm dứt hợp đồng.
    /// </summary>
    public DateTime? TerminatedAt => _terminatedAt;

    /// <summary>
    /// EN: Creation timestamp.
    /// VI: Thời gian tạo.
    /// </summary>
    public DateTime CreatedAt => _createdAt;

    /// <summary>
    /// EN: Last update timestamp.
    /// VI: Thời gian cập nhật cuối.
    /// </summary>
    public DateTime? UpdatedAt => _updatedAt;

    public string? FirstName => _firstName;
    public string? LastName => _lastName;
    public string? Address => _address;
    public string? ProfilePhotoUrl => _profilePhotoUrl;
    public string? DocumentFrontUrl => _documentFrontUrl;
    public string? DocumentBackUrl => _documentBackUrl;

    /// <summary>
    /// EN: Private constructor for EF Core.
    /// VI: Constructor private cho EF Core.
    /// </summary>
    protected MerchantStaff()
    {
    }

    /// <summary>
    /// EN: Invite a new staff member.
    /// VI: Mời nhân viên mới.
    /// </summary>
    public static MerchantStaff Invite(
        Guid merchantId,
        string email,
        StaffRole role,
        StaffPermissions permissions = StaffPermissions.None,
        string? firstName = null,
        string? lastName = null)
    {
        if (merchantId == Guid.Empty)
            throw new DomainException("Merchant ID cannot be empty");
        if (string.IsNullOrWhiteSpace(email))
            throw new DomainException("Email cannot be empty");
        ArgumentNullException.ThrowIfNull(role, nameof(role));

        var staff = new MerchantStaff
        {
            Id = Guid.NewGuid(),
            _userId = Guid.Empty, // Will be set when invitation is accepted
            _merchantId = merchantId,
            _email = email.Trim().ToLowerInvariant(),
            _role = role,
            RoleId = role.Id,
            _status = StaffStatus.Invited,
            StatusId = StaffStatus.Invited.Id,
            _permissions = permissions,
            _firstName = firstName?.Trim(),
            _lastName = lastName?.Trim(),
            _createdAt = DateTime.UtcNow
        };

        staff.AddDomainEvent(new StaffInvitedDomainEvent(staff));
        return staff;
    }

    /// <summary>
    /// EN: Create an active staff member directly (when owner creates account for staff, no invite needed).
    /// VI: Tạo nhân viên active trực tiếp (khi chủ DN tạo tài khoản cho NV, không cần mời).
    /// </summary>
    public static MerchantStaff CreateActive(
        Guid merchantId,
        Guid userId,
        string email,
        StaffRole role,
        StaffPermissions permissions = StaffPermissions.None,
        string? firstName = null,
        string? lastName = null)
    {
        if (merchantId == Guid.Empty)
            throw new DomainException("Merchant ID cannot be empty");
        if (userId == Guid.Empty)
            throw new DomainException("User ID cannot be empty");
        if (string.IsNullOrWhiteSpace(email))
            throw new DomainException("Email cannot be empty");
        ArgumentNullException.ThrowIfNull(role, nameof(role));

        var staff = new MerchantStaff
        {
            Id = Guid.NewGuid(),
            _userId = userId,
            _merchantId = merchantId,
            _email = email.Trim().ToLowerInvariant(),
            _role = role,
            RoleId = role.Id,
            _status = StaffStatus.Active,
            StatusId = StaffStatus.Active.Id,
            _permissions = permissions,
            _firstName = firstName?.Trim(),
            _lastName = lastName?.Trim(),
            _createdAt = DateTime.UtcNow,
            _joinedAt = DateTime.UtcNow
        };

        staff.AddDomainEvent(new StaffJoinedDomainEvent(staff));
        return staff;
    }

    /// <summary>
    /// EN: Accept invitation and link to IAM user.
    /// VI: Chấp nhận lời mời và liên kết với IAM user.
    /// </summary>
    public void AcceptInvitation(Guid userId)
    {
        if (_status != StaffStatus.Invited)
            throw new DomainException("Can only accept pending invitations");
        if (userId == Guid.Empty)
            throw new DomainException("User ID cannot be empty");

        _userId = userId;
        _status = StaffStatus.Active;
        StatusId = StaffStatus.Active.Id;
        _joinedAt = DateTime.UtcNow;
        _updatedAt = DateTime.UtcNow;

        AddDomainEvent(new StaffJoinedDomainEvent(this));
    }

    /// <summary>
    /// EN: Update staff information.
    /// VI: Cập nhật thông tin nhân viên.
    /// </summary>
    public void Update(
        string? employeeCode,
        string? phone,
        string? firstName = null,
        string? lastName = null,
        string? address = null,
        string? profilePhotoUrl = null,
        string? documentFrontUrl = null,
        string? documentBackUrl = null)
    {
        _employeeCode = employeeCode?.Trim();
        _phone = phone?.Trim();
        _firstName = firstName?.Trim();
        _lastName = lastName?.Trim();
        _address = address?.Trim();
        _profilePhotoUrl = profilePhotoUrl?.Trim();
        _documentFrontUrl = documentFrontUrl?.Trim();
        _documentBackUrl = documentBackUrl?.Trim();
        _updatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Update staff role.
    /// VI: Cập nhật vai trò nhân viên.
    /// </summary>
    public void UpdateRole(StaffRole role)
    {
        ArgumentNullException.ThrowIfNull(role, nameof(role));
        _role = role;
        RoleId = role.Id;
        _updatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Update staff permissions.
    /// VI: Cập nhật quyền hạn nhân viên.
    /// </summary>
    public void UpdatePermissions(StaffPermissions permissions)
    {
        _permissions = permissions;
        _updatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Set PIN code for POS authentication.
    /// VI: Đặt mã PIN cho xác thực POS.
    /// </summary>
    public void SetPinCode(string pin)
    {
        if (string.IsNullOrWhiteSpace(pin))
            throw new DomainException("PIN cannot be empty");
        if (pin.Length < 4 || pin.Length > 6)
            throw new DomainException("PIN must be 4-6 digits");
        if (!pin.All(char.IsDigit))
            throw new DomainException("PIN must contain only digits");

        _pinCodeHash = HashPin(pin);
        _updatedAt = DateTime.UtcNow;

        AddDomainEvent(new StaffPinCodeSetDomainEvent(this));
    }

    /// <summary>
    /// EN: Verify PIN code for POS login.
    /// VI: Xác minh mã PIN cho đăng nhập POS.
    /// </summary>
    public bool VerifyPinCode(string pin)
    {
        if (string.IsNullOrEmpty(_pinCodeHash))
            return false;

        var hash = HashPin(pin);
        return hash == _pinCodeHash;
    }

    /// <summary>
    /// EN: Register a device for POS/push notifications.
    /// VI: Đăng ký thiết bị cho POS/push notifications.
    /// </summary>
    public DeviceToken RegisterDevice(string deviceId, string? deviceName, string? fcmToken, string platform)
    {
        var existingToken = _deviceTokens.FirstOrDefault(t => t.DeviceId == deviceId);
        if (existingToken != null)
        {
            existingToken.UpdateFcmToken(fcmToken);
            existingToken.UpdateDeviceName(deviceName);
            existingToken.MarkUsed();
            return existingToken;
        }

        var token = new DeviceToken(Id, deviceId, deviceName, fcmToken, platform);
        _deviceTokens.Add(token);
        _updatedAt = DateTime.UtcNow;

        AddDomainEvent(new StaffDeviceRegisteredDomainEvent(this, token));
        return token;
    }

    /// <summary>
    /// EN: Remove a registered device.
    /// VI: Xóa thiết bị đã đăng ký.
    /// </summary>
    public void RemoveDevice(string deviceId)
    {
        var token = _deviceTokens.FirstOrDefault(t => t.DeviceId == deviceId);
        if (token != null)
        {
            _deviceTokens.Remove(token);
            _updatedAt = DateTime.UtcNow;
        }
    }

    /// <summary>
    /// EN: Assign staff to a shop.
    /// VI: Gán nhân viên vào shop.
    /// </summary>
    public ShopMember AssignToShop(Guid shopId, ShopRole role, Guid? branchId = null, bool isPrimary = false)
    {
        if (_shopAssignments.Any(a => a.ShopId == shopId))
            throw new DomainException("Staff is already assigned to this shop");

        // If setting as primary, unset other primaries
        if (isPrimary)
        {
            foreach (var assignment in _shopAssignments)
            {
                assignment.UnsetAsPrimary();
            }
        }

        var shopMember = new ShopMember(Id, shopId, role, branchId, isPrimary);
        _shopAssignments.Add(shopMember);
        _updatedAt = DateTime.UtcNow;

        AddDomainEvent(new StaffAssignedToShopDomainEvent(this, shopMember));
        return shopMember;
    }

    /// <summary>
    /// EN: Remove shop assignment.
    /// VI: Xóa gán shop.
    /// </summary>
    public void RemoveFromShop(Guid shopId)
    {
        var assignment = _shopAssignments.FirstOrDefault(a => a.ShopId == shopId);
        if (assignment != null)
        {
            _shopAssignments.Remove(assignment);
            _updatedAt = DateTime.UtcNow;
        }
    }

    /// <summary>
    /// EN: Deactivate staff.
    /// VI: Vô hiệu hóa nhân viên.
    /// </summary>
    public void Deactivate()
    {
        if (_status == StaffStatus.Terminated)
            throw new DomainException("Cannot deactivate terminated staff");

        _status = StaffStatus.Inactive;
        StatusId = StaffStatus.Inactive.Id;
        _updatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Reactivate staff.
    /// VI: Kích hoạt lại nhân viên.
    /// </summary>
    public void Reactivate()
    {
        if (_status != StaffStatus.Inactive)
            throw new DomainException("Can only reactivate inactive staff");

        _status = StaffStatus.Active;
        StatusId = StaffStatus.Active.Id;
        _updatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// EN: Terminate employment.
    /// VI: Chấm dứt hợp đồng.
    /// </summary>
    public void Terminate()
    {
        if (_status == StaffStatus.Terminated)
            throw new DomainException("Staff is already terminated");

        _status = StaffStatus.Terminated;
        StatusId = StaffStatus.Terminated.Id;
        _terminatedAt = DateTime.UtcNow;
        _updatedAt = DateTime.UtcNow;

        AddDomainEvent(new StaffTerminatedDomainEvent(this));
    }

    /// <summary>
    /// EN: Check if staff has a specific permission.
    /// VI: Kiểm tra nhân viên có quyền cụ thể không.
    /// </summary>
    public bool HasPermission(StaffPermissions permission)
    {
        if (_permissions == StaffPermissions.All)
            return true;
        return (_permissions & permission) == permission;
    }

    /// <summary>
    /// EN: Hash PIN code.
    /// VI: Hash mã PIN.
    /// </summary>
    private static string HashPin(string pin)
    {
        var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(pin));
        return Convert.ToBase64String(bytes);
    }
}
