namespace WalletService.API.Application.Commands;

using MediatR;
using WalletService.Domain.AggregatesModel.WalletAggregate;
using WalletService.Domain.Exceptions;
using WalletService.Domain.SeedWork;

/// <summary>
/// EN: Handler for CreateHoldCommand - creates an escrow hold on a wallet.
/// VI: Handler cho CreateHoldCommand - tạo escrow hold trên ví.
/// </summary>
public class CreateHoldCommandHandler : IRequestHandler<CreateHoldCommand, HoldResult>
{
    private readonly IWalletRepository _walletRepository;
    private readonly ILogger<CreateHoldCommandHandler> _logger;

    public CreateHoldCommandHandler(
        IWalletRepository walletRepository,
        ILogger<CreateHoldCommandHandler> logger)
    {
        _walletRepository = walletRepository;
        _logger = logger;
    }

    public async Task<HoldResult> Handle(CreateHoldCommand request, CancellationToken cancellationToken)
    {
        // EN: Get wallet by user ID
        // VI: Lấy ví theo user ID
        var wallet = await _walletRepository.GetByUserIdAsync(request.UserId)
            ?? throw new WalletDomainException($"Wallet not found for user {request.UserId}");

        // EN: Get currency type
        // VI: Lấy loại tiền tệ
        var currencyType = Enumeration.FromDisplayName<CurrencyType>(request.CurrencyCode)
            ?? throw new WalletDomainException($"Unknown currency: {request.CurrencyCode}");

        // EN: Create hold
        // VI: Tạo hold
        var hold = wallet.Hold(
            request.Amount,
            currencyType,
            request.ReferenceType,
            request.ReferenceId,
            request.Description,
            request.ExpiresAt);

        _walletRepository.Update(wallet);
        await _walletRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        _logger.LogInformation(
            "Created hold {HoldId} for {Amount} {Currency} on wallet {WalletId}, Reference: {ReferenceType}:{ReferenceId}",
            hold.Id, request.Amount, currencyType.Name, wallet.Id, request.ReferenceType, request.ReferenceId);

        return MapToResult(hold, wallet.Id);
    }

    private static HoldResult MapToResult(HoldItem hold, Guid walletId) => new(
        hold.Id,
        walletId,
        hold.OriginalAmount,
        hold.RemainingAmount,
        hold.ExecutedAmount,
        hold.ReleasedAmount,
        hold.Status.Name,
        hold.CurrencyType.Name,
        hold.ReferenceType,
        hold.ReferenceId,
        hold.CreatedAt,
        hold.UpdatedAt
    );
}

/// <summary>
/// EN: Handler for ExecuteHoldCommand - executes (commits) a portion of an escrow hold.
/// VI: Handler cho ExecuteHoldCommand - thực thi (cam kết) một phần escrow hold.
/// </summary>
public class ExecuteHoldCommandHandler : IRequestHandler<ExecuteHoldCommand, HoldResult>
{
    private readonly IWalletRepository _walletRepository;
    private readonly ILogger<ExecuteHoldCommandHandler> _logger;

    public ExecuteHoldCommandHandler(
        IWalletRepository walletRepository,
        ILogger<ExecuteHoldCommandHandler> logger)
    {
        _walletRepository = walletRepository;
        _logger = logger;
    }

    public async Task<HoldResult> Handle(ExecuteHoldCommand request, CancellationToken cancellationToken)
    {
        // EN: Get wallet
        // VI: Lấy ví
        var wallet = await _walletRepository.GetByIdAsync(request.WalletId)
            ?? throw new WalletDomainException($"Wallet {request.WalletId} not found");

        // EN: Execute hold
        // VI: Thực thi hold
        wallet.ExecuteHold(request.HoldId, request.Amount, request.ExecutionRef);

        _walletRepository.Update(wallet);
        await _walletRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        var hold = wallet.GetHold(request.HoldId)!;

        _logger.LogInformation(
            "Executed {Amount} from hold {HoldId} on wallet {WalletId}, Remaining: {Remaining}",
            request.Amount, request.HoldId, wallet.Id, hold.RemainingAmount);

        return MapToResult(hold, wallet.Id);
    }

    private static HoldResult MapToResult(HoldItem hold, Guid walletId) => new(
        hold.Id,
        walletId,
        hold.OriginalAmount,
        hold.RemainingAmount,
        hold.ExecutedAmount,
        hold.ReleasedAmount,
        hold.Status.Name,
        hold.CurrencyType.Name,
        hold.ReferenceType,
        hold.ReferenceId,
        hold.CreatedAt,
        hold.UpdatedAt
    );
}

/// <summary>
/// EN: Handler for ReleaseHoldCommand - releases (returns) a portion of an escrow hold.
/// VI: Handler cho ReleaseHoldCommand - giải phóng (trả lại) một phần escrow hold.
/// </summary>
public class ReleaseHoldCommandHandler : IRequestHandler<ReleaseHoldCommand, HoldResult>
{
    private readonly IWalletRepository _walletRepository;
    private readonly ILogger<ReleaseHoldCommandHandler> _logger;

    public ReleaseHoldCommandHandler(
        IWalletRepository walletRepository,
        ILogger<ReleaseHoldCommandHandler> logger)
    {
        _walletRepository = walletRepository;
        _logger = logger;
    }

    public async Task<HoldResult> Handle(ReleaseHoldCommand request, CancellationToken cancellationToken)
    {
        // EN: Get wallet
        // VI: Lấy ví
        var wallet = await _walletRepository.GetByIdAsync(request.WalletId)
            ?? throw new WalletDomainException($"Wallet {request.WalletId} not found");

        // EN: Release hold
        // VI: Giải phóng hold
        wallet.ReleaseHold(request.HoldId, request.Amount);

        _walletRepository.Update(wallet);
        await _walletRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        var hold = wallet.GetHold(request.HoldId)!;

        _logger.LogInformation(
            "Released {Amount} from hold {HoldId} on wallet {WalletId}, Remaining: {Remaining}",
            request.Amount ?? hold.OriginalAmount, request.HoldId, wallet.Id, hold.RemainingAmount);

        return MapToResult(hold, wallet.Id);
    }

    private static HoldResult MapToResult(HoldItem hold, Guid walletId) => new(
        hold.Id,
        walletId,
        hold.OriginalAmount,
        hold.RemainingAmount,
        hold.ExecutedAmount,
        hold.ReleasedAmount,
        hold.Status.Name,
        hold.CurrencyType.Name,
        hold.ReferenceType,
        hold.ReferenceId,
        hold.CreatedAt,
        hold.UpdatedAt
    );
}

/// <summary>
/// EN: Handler for CancelHoldCommand - cancels an escrow hold and releases all remaining.
/// VI: Handler cho CancelHoldCommand - hủy escrow hold và giải phóng tất cả còn lại.
/// </summary>
public class CancelHoldCommandHandler : IRequestHandler<CancelHoldCommand, HoldResult>
{
    private readonly IWalletRepository _walletRepository;
    private readonly ILogger<CancelHoldCommandHandler> _logger;

    public CancelHoldCommandHandler(
        IWalletRepository walletRepository,
        ILogger<CancelHoldCommandHandler> logger)
    {
        _walletRepository = walletRepository;
        _logger = logger;
    }

    public async Task<HoldResult> Handle(CancelHoldCommand request, CancellationToken cancellationToken)
    {
        // EN: Get wallet
        // VI: Lấy ví
        var wallet = await _walletRepository.GetByIdAsync(request.WalletId)
            ?? throw new WalletDomainException($"Wallet {request.WalletId} not found");

        // EN: Cancel hold
        // VI: Hủy hold
        wallet.CancelHold(request.HoldId);

        _walletRepository.Update(wallet);
        await _walletRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);

        var hold = wallet.GetHold(request.HoldId)!;

        _logger.LogInformation("Cancelled hold {HoldId} on wallet {WalletId}", request.HoldId, wallet.Id);

        return MapToResult(hold, wallet.Id);
    }

    private static HoldResult MapToResult(HoldItem hold, Guid walletId) => new(
        hold.Id,
        walletId,
        hold.OriginalAmount,
        hold.RemainingAmount,
        hold.ExecutedAmount,
        hold.ReleasedAmount,
        hold.Status.Name,
        hold.CurrencyType.Name,
        hold.ReferenceType,
        hold.ReferenceId,
        hold.CreatedAt,
        hold.UpdatedAt
    );
}
