// EN: Staff management commands.
// VI: Commands quản lý nhân viên.

using MediatR;
using MerchantService.Domain.AggregatesModel.MerchantAggregate;
using MerchantService.Domain.AggregatesModel.MerchantStaffAggregate;
using MerchantService.Domain.Exceptions;

namespace MerchantService.API.Application.Commands.Staff;

#region Invite Staff Command

/// <summary>
/// EN: Command to invite a new staff member.
/// VI: Command để mời nhân viên mới.
/// </summary>
public record InviteStaffCommand : IRequest<InviteStaffResult>
{
    public string Email { get; init; } = null!;
    public string Role { get; init; } = "Cashier";
    public Guid? ShopId { get; init; }
    public Guid? BranchId { get; init; }
}

public record InviteStaffResult(Guid StaffId, string Email, string Status);

/// <summary>
/// EN: Handler for InviteStaffCommand.
/// VI: Handler cho InviteStaffCommand.
/// </summary>
public class InviteStaffCommandHandler : IRequestHandler<InviteStaffCommand, InviteStaffResult>
{
    private readonly IMerchantRepository _merchantRepository;
    private readonly IMerchantStaffRepository _staffRepository;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<InviteStaffCommandHandler> _logger;

    public InviteStaffCommandHandler(
        IMerchantRepository merchantRepository,
        IMerchantStaffRepository staffRepository,
        IHttpContextAccessor httpContextAccessor,
        ILogger<InviteStaffCommandHandler> logger)
    {
        _merchantRepository = merchantRepository;
        _staffRepository = staffRepository;
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
    }

    public async Task<InviteStaffResult> Handle(InviteStaffCommand request, CancellationToken cancellationToken)
    {
        var userId = GetUserId();
        var merchant = await _merchantRepository.GetByUserIdAsync(userId, cancellationToken)
            ?? throw new DomainException("Merchant not found");

        // EN: Parse role
        // VI: Parse vai trò
        var role = request.Role.ToLowerInvariant() switch
        {
            "cashier" => StaffRole.Cashier,
            "waiter" => StaffRole.Waiter,
            "manager" => StaffRole.Manager,
            "admin" => StaffRole.Admin,
            _ => StaffRole.Cashier
        };

        // EN: Create invitation
        // VI: Tạo lời mời
        var staff = MerchantStaff.Invite(
            merchant.Id,
            request.Email,
            role,
            StaffPermissions.ViewSales | StaffPermissions.ProcessPayment);

        _staffRepository.Add(staff);
        await _staffRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation("Staff invited: {StaffId} to merchant {MerchantId}", staff.Id, merchant.Id);

        return new InviteStaffResult(staff.Id, staff.Email ?? request.Email, staff.Status.Name);
    }

    private Guid GetUserId()
    {
        var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
            ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
        if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
            throw new DomainException("User not authenticated");
        return userId;
    }
}

#endregion

#region Update Staff Command

/// <summary>
/// EN: Command to update a staff member.
/// VI: Command để cập nhật nhân viên.
/// </summary>
public record UpdateStaffCommand : IRequest<bool>
{
    public Guid StaffId { get; init; }
    public string? EmployeeCode { get; init; }
    public string? Phone { get; init; }
    public string? Role { get; init; }
}

/// <summary>
/// EN: Handler for UpdateStaffCommand.
/// VI: Handler cho UpdateStaffCommand.
/// </summary>
public class UpdateStaffCommandHandler : IRequestHandler<UpdateStaffCommand, bool>
{
    private readonly IMerchantRepository _merchantRepository;
    private readonly IMerchantStaffRepository _staffRepository;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<UpdateStaffCommandHandler> _logger;

    public UpdateStaffCommandHandler(
        IMerchantRepository merchantRepository,
        IMerchantStaffRepository staffRepository,
        IHttpContextAccessor httpContextAccessor,
        ILogger<UpdateStaffCommandHandler> logger)
    {
        _merchantRepository = merchantRepository;
        _staffRepository = staffRepository;
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
    }

    public async Task<bool> Handle(UpdateStaffCommand request, CancellationToken cancellationToken)
    {
        var userId = GetUserId();
        var merchant = await _merchantRepository.GetByUserIdAsync(userId, cancellationToken)
            ?? throw new DomainException("Merchant not found");

        var staff = await _staffRepository.GetByIdAsync(request.StaffId, cancellationToken)
            ?? throw new DomainException("Staff not found");

        if (staff.MerchantId != merchant.Id)
            throw new DomainException("You do not have permission to update this staff");

        staff.Update(request.EmployeeCode, request.Phone);

        if (!string.IsNullOrEmpty(request.Role))
        {
            var role = request.Role.ToLowerInvariant() switch
            {
                "cashier" => StaffRole.Cashier,
                "waiter" => StaffRole.Waiter,
                "manager" => StaffRole.Manager,
                "admin" => StaffRole.Admin,
                _ => null
            };
            if (role != null)
                staff.UpdateRole(role);
        }

        _staffRepository.Update(staff);
        await _staffRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation("Staff updated: {StaffId}", request.StaffId);
        return true;
    }

    private Guid GetUserId()
    {
        var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
            ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
        if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
            throw new DomainException("User not authenticated");
        return userId;
    }
}

#endregion

#region Delete Staff Command

/// <summary>
/// EN: Command to delete/terminate a staff member.
/// VI: Command để xóa/chấm dứt nhân viên.
/// </summary>
public record DeleteStaffCommand(Guid StaffId) : IRequest<bool>;

/// <summary>
/// EN: Handler for DeleteStaffCommand.
/// VI: Handler cho DeleteStaffCommand.
/// </summary>
public class DeleteStaffCommandHandler : IRequestHandler<DeleteStaffCommand, bool>
{
    private readonly IMerchantRepository _merchantRepository;
    private readonly IMerchantStaffRepository _staffRepository;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<DeleteStaffCommandHandler> _logger;

    public DeleteStaffCommandHandler(
        IMerchantRepository merchantRepository,
        IMerchantStaffRepository staffRepository,
        IHttpContextAccessor httpContextAccessor,
        ILogger<DeleteStaffCommandHandler> logger)
    {
        _merchantRepository = merchantRepository;
        _staffRepository = staffRepository;
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
    }

    public async Task<bool> Handle(DeleteStaffCommand request, CancellationToken cancellationToken)
    {
        var userId = GetUserId();
        var merchant = await _merchantRepository.GetByUserIdAsync(userId, cancellationToken)
            ?? throw new DomainException("Merchant not found");

        var staff = await _staffRepository.GetByIdAsync(request.StaffId, cancellationToken)
            ?? throw new DomainException("Staff not found");

        if (staff.MerchantId != merchant.Id)
            throw new DomainException("You do not have permission to delete this staff");

        staff.Terminate();

        _staffRepository.Update(staff);
        await _staffRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation("Staff terminated: {StaffId}", request.StaffId);
        return true;
    }

    private Guid GetUserId()
    {
        var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
            ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
        if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
            throw new DomainException("User not authenticated");
        return userId;
    }
}

#endregion

#region Accept Invite Command

/// <summary>
/// EN: Command to accept a staff invitation.
/// VI: Command để chấp nhận lời mời làm nhân viên.
/// </summary>
public record AcceptInviteCommand : IRequest<AcceptInviteResult>
{
    public string InvitationToken { get; init; } = null!;
    public string PinCode { get; init; } = null!;
}

public record AcceptInviteResult(Guid StaffId, string Status);

/// <summary>
/// EN: Handler for AcceptInviteCommand.
/// VI: Handler cho AcceptInviteCommand.
/// </summary>
public class AcceptInviteCommandHandler : IRequestHandler<AcceptInviteCommand, AcceptInviteResult>
{
    private readonly IMerchantStaffRepository _staffRepository;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILogger<AcceptInviteCommandHandler> _logger;

    public AcceptInviteCommandHandler(
        IMerchantStaffRepository staffRepository,
        IHttpContextAccessor httpContextAccessor,
        ILogger<AcceptInviteCommandHandler> logger)
    {
        _staffRepository = staffRepository;
        _httpContextAccessor = httpContextAccessor;
        _logger = logger;
    }

    public async Task<AcceptInviteResult> Handle(AcceptInviteCommand request, CancellationToken cancellationToken)
    {
        var userId = GetUserId();

        // EN: Find staff by invitation token (simplified - in real app use secure token)
        // VI: Tìm nhân viên theo token mời (đơn giản - app thực tế dùng token bảo mật)
        if (!Guid.TryParse(request.InvitationToken, out var staffId))
            throw new DomainException("Invalid invitation token");

        var staff = await _staffRepository.GetByIdAsync(staffId, cancellationToken)
            ?? throw new DomainException("Invitation not found");

        if (staff.Status != StaffStatus.Invited)
            throw new DomainException("Invitation is no longer valid");

        // EN: Accept invitation and set PIN
        // VI: Chấp nhận lời mời và đặt PIN
        staff.AcceptInvitation(userId);
        staff.SetPinCode(request.PinCode);

        _staffRepository.Update(staff);
        await _staffRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation("Staff invitation accepted: {StaffId} by user {UserId}", staffId, userId);

        return new AcceptInviteResult(staff.Id, staff.Status.Name);
    }

    private Guid GetUserId()
    {
        var userIdClaim = _httpContextAccessor.HttpContext?.User.FindFirst("sub")?.Value
            ?? _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
        if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
            throw new DomainException("User not authenticated");
        return userId;
    }
}

#endregion
