using Microsoft.EntityFrameworkCore;
using Tiku.Application.Commerce;
using Tiku.Domain.Commerce;

namespace Tiku.Infrastructure.Commerce;

public sealed partial class CommerceService
{
    public async Task<CommerceCouponItem> ClaimCouponAsync(
        CommerceActor actor,
        ClaimCommerceCouponCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertActiveMemberAsync(actor, cancellationToken);
        var coupon = await FindCouponByCodeAsync(actor.TenantId, command.CouponCode, cancellationToken);
        ValidateCouponClaimable(coupon, null);

        var existing = await dbContext.CouponRedemptions
            .AsNoTracking()
            .Where(item =>
                item.TenantId == actor.TenantId &&
                item.UserId == actor.UserId &&
                item.CouponId == coupon.Id)
            .OrderByDescending(item => item.CreatedAt)
            .FirstOrDefaultAsync(cancellationToken);
        if (existing is not null) return ToCouponItem(coupon, existing, null);

        if (coupon.MaxUses.HasValue && coupon.UsedCount >= coupon.MaxUses.Value)
            throw new CommerceException("Coupon usage limit has been reached.", "coupon_usage_limit_reached");

        var redemption = new CouponRedemption
        {
            TenantId = actor.TenantId,
            CouponId = coupon.Id,
            UserId = actor.UserId,
            PlanId = coupon.PlanId,
            CouponCode = coupon.Code,
            Status = CouponRedemptionStatus.Claimed,
            Source = "student_claim",
            ClaimedAt = DateTimeOffset.UtcNow
        };
        coupon.UsedCount += 1;
        dbContext.CouponRedemptions.Add(redemption);
        await dbContext.SaveChangesAsync(cancellationToken);
        return ToCouponItem(coupon, redemption, null);
    }

    public async Task<CommerceCouponList> GetCouponsAsync(
        CommerceActor actor,
        CommerceCouponQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertActiveMemberAsync(actor, cancellationToken);
        var redemptions = dbContext.CouponRedemptions
            .AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId && item.UserId == actor.UserId);
        if (!string.IsNullOrWhiteSpace(query.Status))
            redemptions = redemptions.Where(item => item.Status == ParseCouponRedemptionStatus(query.Status));

        var items = await redemptions
            .OrderByDescending(item => item.CreatedAt)
            .Take(Math.Clamp(query.Limit ?? 50, 1, 100))
            .ToArrayAsync(cancellationToken);
        var couponIds = items
            .Where(item => item.CouponId.HasValue)
            .Select(item => item.CouponId!.Value)
            .Distinct()
            .ToArray();
        var coupons = await dbContext.Coupons
            .AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId && couponIds.Contains(item.Id))
            .ToDictionaryAsync(item => item.Id, cancellationToken);

        return new CommerceCouponList(
            items.Select(item =>
            {
                coupons.TryGetValue(item.CouponId ?? Guid.Empty, out var coupon);
                return ToCouponItem(coupon, item, item.DiscountAppliedCents);
            }).ToArray());
    }

    public async Task<CommerceCouponCheckResult> CheckCouponAsync(
        CommerceActor actor,
        CheckCommerceCouponCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertActiveMemberAsync(actor, cancellationToken);
        if (command.Quantity is < 1 or > 99)
            throw new CommerceException("Quantity must be between 1 and 99.", "invalid_quantity");

        var plan = await dbContext.SvipPlans
                       .AsNoTracking()
                       .SingleOrDefaultAsync(item =>
                               item.TenantId == actor.TenantId &&
                               item.Id == command.PlanId &&
                               item.IsActive,
                           cancellationToken)
                   ?? throw new CommerceException("SVIP plan was not found.", "svip_plan_not_found");
        var originalAmountCents = checked(plan.PriceCents * command.Quantity);
        try
        {
            var coupon = await ResolveCouponForCheckAsync(actor, command, plan, originalAmountCents, cancellationToken);
            return new CommerceCouponCheckResult(
                true,
                null,
                coupon.Coupon.Id,
                coupon.Redemption.Id,
                coupon.Coupon.Code,
                originalAmountCents,
                coupon.DiscountCents,
                Math.Max(0, originalAmountCents - coupon.DiscountCents),
                FormatCny(Math.Max(0, originalAmountCents - coupon.DiscountCents)));
        }
        catch (CommerceException exception) when (exception.Code.StartsWith("coupon_", StringComparison.Ordinal))
        {
            return new CommerceCouponCheckResult(
                false,
                exception.Code,
                null,
                command.CouponRedemptionId,
                command.CouponCode,
                originalAmountCents,
                0,
                originalAmountCents,
                FormatCny(originalAmountCents));
        }
    }
}