using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Membership.Entities;
using TakeoutSaaS.Domain.Membership.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;

namespace TakeoutSaaS.Infrastructure.App.Repositories;

/// <summary>
/// 储值卡模块 EF Core 仓储实现。
/// </summary>
public sealed class EfStoredCardRepository(TakeoutAppDbContext context) : IStoredCardRepository
{
    /// <inheritdoc />
    public async Task<IReadOnlyList<MemberStoredCardPlan>> GetPlansByStoreAsync(
        long tenantId,
        long storeId,
        CancellationToken cancellationToken = default)
    {
        return await context.MemberStoredCardPlans
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId && item.StoreId == storeId)
            .OrderBy(item => item.SortOrder)
            .ThenBy(item => item.RechargeAmount)
            .ThenBy(item => item.Id)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task<MemberStoredCardPlan?> FindPlanByIdAsync(
        long tenantId,
        long storeId,
        long planId,
        CancellationToken cancellationToken = default)
    {
        return context.MemberStoredCardPlans
            .Where(item =>
                item.TenantId == tenantId &&
                item.StoreId == storeId &&
                item.Id == planId)
            .FirstOrDefaultAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task AddPlanAsync(MemberStoredCardPlan entity, CancellationToken cancellationToken = default)
    {
        return context.MemberStoredCardPlans.AddAsync(entity, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task UpdatePlanAsync(MemberStoredCardPlan entity, CancellationToken cancellationToken = default)
    {
        context.MemberStoredCardPlans.Update(entity);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task DeletePlanAsync(MemberStoredCardPlan entity, CancellationToken cancellationToken = default)
    {
        context.MemberStoredCardPlans.Remove(entity);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public async Task<Dictionary<long, MemberStoredCardPlanAggregateSnapshot>> GetPlanAggregatesAsync(
        long tenantId,
        long storeId,
        IReadOnlyCollection<long> planIds,
        CancellationToken cancellationToken = default)
    {
        if (planIds.Count == 0)
        {
            return [];
        }

        var aggregates = await context.MemberStoredCardRechargeRecords
            .AsNoTracking()
            .Where(item =>
                item.TenantId == tenantId &&
                item.StoreId == storeId &&
                item.PlanId.HasValue &&
                planIds.Contains(item.PlanId.Value))
            .GroupBy(item => item.PlanId!.Value)
            .Select(group => new MemberStoredCardPlanAggregateSnapshot
            {
                PlanId = group.Key,
                RechargeCount = group.Count(),
                TotalRechargeAmount = group.Sum(item => item.RechargeAmount)
            })
            .ToListAsync(cancellationToken);

        return aggregates.ToDictionary(item => item.PlanId, item => item);
    }

    /// <inheritdoc />
    public async Task<MemberStoredCardPlanStatsSnapshot> GetPlanStatsAsync(
        long tenantId,
        long storeId,
        DateTime nowUtc,
        CancellationToken cancellationToken = default)
    {
        var utcNow = NormalizeUtc(nowUtc);
        var monthStart = new DateTime(utcNow.Year, utcNow.Month, 1, 0, 0, 0, DateTimeKind.Utc);

        var summary = await context.MemberStoredCardRechargeRecords
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId && item.StoreId == storeId)
            .GroupBy(_ => 1)
            .Select(group => new
            {
                TotalRechargeAmount = group.Sum(item => item.RechargeAmount),
                TotalGiftAmount = group.Sum(item => item.GiftAmount),
                CurrentMonthRechargeAmount = group
                    .Where(item => item.RechargedAt >= monthStart && item.RechargedAt <= utcNow)
                    .Sum(item => item.RechargeAmount),
                RechargeMemberCount = group.Select(item => item.MemberId).Distinct().Count()
            })
            .FirstOrDefaultAsync(cancellationToken);

        if (summary is null)
        {
            return new MemberStoredCardPlanStatsSnapshot();
        }

        return new MemberStoredCardPlanStatsSnapshot
        {
            TotalRechargeAmount = summary.TotalRechargeAmount,
            TotalGiftAmount = summary.TotalGiftAmount,
            CurrentMonthRechargeAmount = summary.CurrentMonthRechargeAmount,
            RechargeMemberCount = summary.RechargeMemberCount
        };
    }

    /// <inheritdoc />
    public async Task<(IReadOnlyList<MemberStoredCardRechargeRecord> Items, int TotalCount)> SearchRechargeRecordsAsync(
        long tenantId,
        long storeId,
        DateTime? startUtc,
        DateTime? endUtc,
        string? keyword,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        var normalizedPage = Math.Max(1, page);
        var normalizedPageSize = Math.Clamp(pageSize, 1, 500);

        var query = BuildRechargeRecordQuery(
            tenantId,
            storeId,
            startUtc,
            endUtc,
            keyword);

        var totalCount = await query.CountAsync(cancellationToken);
        if (totalCount == 0)
        {
            return ([], 0);
        }

        var items = await query
            .OrderByDescending(item => item.RechargedAt)
            .ThenByDescending(item => item.Id)
            .Skip((normalizedPage - 1) * normalizedPageSize)
            .Take(normalizedPageSize)
            .ToListAsync(cancellationToken);

        return (items, totalCount);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<MemberStoredCardRechargeRecord>> ListRechargeRecordsForExportAsync(
        long tenantId,
        long storeId,
        DateTime? startUtc,
        DateTime? endUtc,
        string? keyword,
        CancellationToken cancellationToken = default)
    {
        return await BuildRechargeRecordQuery(
                tenantId,
                storeId,
                startUtc,
                endUtc,
                keyword)
            .OrderByDescending(item => item.RechargedAt)
            .ThenByDescending(item => item.Id)
            .Take(20_000)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task AddRechargeRecordAsync(MemberStoredCardRechargeRecord entity, CancellationToken cancellationToken = default)
    {
        return context.MemberStoredCardRechargeRecords.AddAsync(entity, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        return context.SaveChangesAsync(cancellationToken);
    }

    private IQueryable<MemberStoredCardRechargeRecord> BuildRechargeRecordQuery(
        long tenantId,
        long storeId,
        DateTime? startUtc,
        DateTime? endUtc,
        string? keyword)
    {
        var query = context.MemberStoredCardRechargeRecords
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId && item.StoreId == storeId);

        if (startUtc.HasValue)
        {
            var normalizedStart = NormalizeUtc(startUtc.Value);
            query = query.Where(item => item.RechargedAt >= normalizedStart);
        }

        if (endUtc.HasValue)
        {
            var normalizedEnd = NormalizeUtc(endUtc.Value);
            query = query.Where(item => item.RechargedAt <= normalizedEnd);
        }

        var normalizedKeyword = (keyword ?? string.Empty).Trim();
        if (!string.IsNullOrWhiteSpace(normalizedKeyword))
        {
            var like = $"%{normalizedKeyword}%";
            query = query.Where(item =>
                EF.Functions.ILike(item.RecordNo, like) ||
                EF.Functions.ILike(item.MemberName, like) ||
                EF.Functions.ILike(item.MemberMobileMasked, like));
        }

        return query;
    }

    private static DateTime NormalizeUtc(DateTime value)
    {
        return value.Kind switch
        {
            DateTimeKind.Utc => value,
            DateTimeKind.Local => value.ToUniversalTime(),
            _ => DateTime.SpecifyKind(value, DateTimeKind.Utc)
        };
    }
}
