using Meezi.Core.Entities;
using Meezi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace Meezi.API.Services;

public sealed record LoyaltyRedeemResult(int PointsUsed, decimal DiscountToman);

public interface ILoyaltyService
{
    /// <summary>Earn points when an order is fully paid (1 point per 10,000 ت paid).</summary>
    Task ApplyEarnOnOrderPaidAsync(string cafeId, string? customerId, decimal paidAmount, CancellationToken ct = default);

    /// <summary>Redeem loyalty points before payment (1 point = 100 ت discount).</summary>
    Task<(bool Success, LoyaltyRedeemResult? Data, string? ErrorCode)> RedeemOnOrderAsync(
        string cafeId,
        Order order,
        int pointsRequested,
        CancellationToken ct = default);
}

public class LoyaltyService : ILoyaltyService
{
    public const decimal TomanPerPointEarn = 10_000m;
    public const decimal TomanPerPointRedeem = 100m;

    private readonly AppDbContext _db;

    public LoyaltyService(AppDbContext db) => _db = db;

    public async Task ApplyEarnOnOrderPaidAsync(
        string cafeId,
        string? customerId,
        decimal paidAmount,
        CancellationToken ct = default)
    {
        if (string.IsNullOrEmpty(customerId) || paidAmount <= 0)
            return;

        var points = (int)Math.Floor(paidAmount / TomanPerPointEarn);
        if (points <= 0)
            return;

        var customer = await _db.Customers
            .FirstOrDefaultAsync(c => c.Id == customerId && c.CafeId == cafeId, ct);
        if (customer is null)
            return;

        customer.LoyaltyPoints += points;
        await _db.SaveChangesAsync(ct);
    }

    public async Task<(bool Success, LoyaltyRedeemResult? Data, string? ErrorCode)> RedeemOnOrderAsync(
        string cafeId,
        Order order,
        int pointsRequested,
        CancellationToken ct = default)
    {
        if (pointsRequested <= 0)
            return (true, new LoyaltyRedeemResult(0, 0), null);

        if (string.IsNullOrEmpty(order.CustomerId))
            return (false, null, "LOYALTY_NO_CUSTOMER");

        var customer = await _db.Customers
            .FirstOrDefaultAsync(c => c.Id == order.CustomerId && c.CafeId == cafeId, ct);
        if (customer is null)
            return (false, null, "LOYALTY_NO_CUSTOMER");

        var paidSoFar = order.Payments
            .Where(p => p.Status == Core.Enums.PaymentStatus.Completed)
            .Sum(p => p.Amount);
        var amountDue = Math.Max(0, order.Total - paidSoFar);
        if (amountDue <= 0)
            return (false, null, "LOYALTY_NOTHING_DUE");

        var maxByDue = (int)Math.Floor(amountDue / TomanPerPointRedeem);
        var points = Math.Min(pointsRequested, Math.Min(customer.LoyaltyPoints, maxByDue));
        if (points <= 0)
            return (false, null, "LOYALTY_INSUFFICIENT_POINTS");

        var discount = points * TomanPerPointRedeem;
        customer.LoyaltyPoints -= points;
        await _db.SaveChangesAsync(ct);

        return (true, new LoyaltyRedeemResult(points, discount), null);
    }
}
