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

namespace TakeoutSaaS.Infrastructure.App.Repositories;

/// <summary>
/// 次卡仓储 EF Core 实现。
/// </summary>
public sealed class EfPunchCardRepository(TakeoutAppDbContext context) : IPunchCardRepository
{
    /// <inheritdoc />
    public async Task<(IReadOnlyList<PunchCardTemplate> Items, int TotalCount)> SearchTemplatesAsync(
        long tenantId,
        long storeId,
        string? keyword,
        PunchCardStatus? status,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        var normalizedPage = Math.Max(1, page);
        var normalizedPageSize = Math.Clamp(pageSize, 1, 200);

        var query = context.PunchCardTemplates
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId && item.StoreId == storeId);

        if (status.HasValue)
        {
            query = query.Where(item => item.Status == status.Value);
        }

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

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

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

        return (items, totalCount);
    }

    /// <inheritdoc />
    public Task<PunchCardTemplate?> FindTemplateByIdAsync(
        long tenantId,
        long storeId,
        long templateId,
        CancellationToken cancellationToken = default)
    {
        return context.PunchCardTemplates
            .Where(item =>
                item.TenantId == tenantId &&
                item.StoreId == storeId &&
                item.Id == templateId)
            .FirstOrDefaultAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<PunchCardTemplate>> GetTemplatesByIdsAsync(
        long tenantId,
        long storeId,
        IReadOnlyCollection<long> templateIds,
        CancellationToken cancellationToken = default)
    {
        if (templateIds.Count == 0)
        {
            return [];
        }

        return await context.PunchCardTemplates
            .AsNoTracking()
            .Where(item =>
                item.TenantId == tenantId &&
                item.StoreId == storeId &&
                templateIds.Contains(item.Id))
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<PunchCardTemplate>> GetTemplatesByStoreAsync(
        long tenantId,
        long storeId,
        CancellationToken cancellationToken = default)
    {
        return await context.PunchCardTemplates
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId && item.StoreId == storeId)
            .OrderByDescending(item => item.UpdatedAt ?? item.CreatedAt)
            .ThenByDescending(item => item.Id)
            .ToListAsync(cancellationToken);
    }

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

        var nowUtc = DateTime.UtcNow;
        var aggregates = await context.PunchCardInstances
            .AsNoTracking()
            .Where(item =>
                item.TenantId == tenantId &&
                item.StoreId == storeId &&
                templateIds.Contains(item.PunchCardTemplateId))
            .GroupBy(item => item.PunchCardTemplateId)
            .Select(group => new PunchCardTemplateAggregateSnapshot
            {
                TemplateId = group.Key,
                SoldCount = group.Count(),
                ActiveCount = group.Count(item =>
                    item.Status == PunchCardInstanceStatus.Active &&
                    item.RemainingTimes > 0 &&
                    (!item.ExpiresAt.HasValue || item.ExpiresAt.Value >= nowUtc)),
                RevenueAmount = group.Sum(item => item.PaidAmount)
            })
            .ToListAsync(cancellationToken);

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

    /// <inheritdoc />
    public async Task<PunchCardTemplateStatsSnapshot> GetTemplateStatsAsync(
        long tenantId,
        long storeId,
        CancellationToken cancellationToken = default)
    {
        var onSaleCount = await context.PunchCardTemplates
            .AsNoTracking()
            .Where(item =>
                item.TenantId == tenantId &&
                item.StoreId == storeId &&
                item.Status == PunchCardStatus.Enabled)
            .CountAsync(cancellationToken);

        var summary = await context.PunchCardInstances
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId && item.StoreId == storeId)
            .GroupBy(_ => 1)
            .Select(group => new
            {
                TotalSoldCount = group.Count(),
                TotalRevenueAmount = group.Sum(item => item.PaidAmount)
            })
            .FirstOrDefaultAsync(cancellationToken);

        var nowUtc = DateTime.UtcNow;
        var activeInUseCount = await context.PunchCardInstances
            .AsNoTracking()
            .Where(item =>
                item.TenantId == tenantId &&
                item.StoreId == storeId &&
                item.Status == PunchCardInstanceStatus.Active &&
                item.RemainingTimes > 0 &&
                (!item.ExpiresAt.HasValue || item.ExpiresAt.Value >= nowUtc))
            .CountAsync(cancellationToken);

        return new PunchCardTemplateStatsSnapshot
        {
            OnSaleCount = onSaleCount,
            TotalSoldCount = summary?.TotalSoldCount ?? 0,
            TotalRevenueAmount = summary?.TotalRevenueAmount ?? 0m,
            ActiveInUseCount = activeInUseCount
        };
    }

    /// <inheritdoc />
    public Task AddTemplateAsync(PunchCardTemplate entity, CancellationToken cancellationToken = default)
    {
        return context.PunchCardTemplates.AddAsync(entity, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task UpdateTemplateAsync(PunchCardTemplate entity, CancellationToken cancellationToken = default)
    {
        context.PunchCardTemplates.Update(entity);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task DeleteTemplateAsync(PunchCardTemplate entity, CancellationToken cancellationToken = default)
    {
        context.PunchCardTemplates.Remove(entity);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task<PunchCardInstance?> FindInstanceByNoAsync(
        long tenantId,
        long storeId,
        string instanceNo,
        CancellationToken cancellationToken = default)
    {
        return context.PunchCardInstances
            .Where(item =>
                item.TenantId == tenantId &&
                item.StoreId == storeId &&
                item.InstanceNo == instanceNo)
            .FirstOrDefaultAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task<PunchCardInstance?> FindInstanceByIdAsync(
        long tenantId,
        long storeId,
        long instanceId,
        CancellationToken cancellationToken = default)
    {
        return context.PunchCardInstances
            .Where(item =>
                item.TenantId == tenantId &&
                item.StoreId == storeId &&
                item.Id == instanceId)
            .FirstOrDefaultAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<PunchCardInstance>> GetInstancesByIdsAsync(
        long tenantId,
        long storeId,
        IReadOnlyCollection<long> instanceIds,
        CancellationToken cancellationToken = default)
    {
        if (instanceIds.Count == 0)
        {
            return [];
        }

        return await context.PunchCardInstances
            .AsNoTracking()
            .Where(item =>
                item.TenantId == tenantId &&
                item.StoreId == storeId &&
                instanceIds.Contains(item.Id))
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task AddInstanceAsync(PunchCardInstance entity, CancellationToken cancellationToken = default)
    {
        return context.PunchCardInstances.AddAsync(entity, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task UpdateInstanceAsync(PunchCardInstance entity, CancellationToken cancellationToken = default)
    {
        context.PunchCardInstances.Update(entity);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public async Task<(IReadOnlyList<PunchCardUsageRecord> Items, int TotalCount)> SearchUsageRecordsAsync(
        long tenantId,
        long storeId,
        long? templateId,
        string? keyword,
        PunchCardUsageRecordFilterStatus? status,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        var normalizedPage = Math.Max(1, page);
        var normalizedPageSize = Math.Clamp(pageSize, 1, 500);
        var query = BuildUsageRecordQuery(
            tenantId,
            storeId,
            templateId,
            keyword,
            status,
            DateTime.UtcNow);

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

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

        return (items, totalCount);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<PunchCardUsageRecord>> ListUsageRecordsForExportAsync(
        long tenantId,
        long storeId,
        long? templateId,
        string? keyword,
        PunchCardUsageRecordFilterStatus? status,
        CancellationToken cancellationToken = default)
    {
        return await BuildUsageRecordQuery(
                tenantId,
                storeId,
                templateId,
                keyword,
                status,
                DateTime.UtcNow)
            .OrderByDescending(item => item.UsedAt)
            .ThenByDescending(item => item.Id)
            .Take(20_000)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<PunchCardUsageStatsSnapshot> GetUsageStatsAsync(
        long tenantId,
        long storeId,
        long? templateId,
        DateTime nowUtc,
        CancellationToken cancellationToken = default)
    {
        var utcNow = NormalizeUtc(nowUtc);
        var todayStart = utcNow.Date;
        var monthStart = new DateTime(utcNow.Year, utcNow.Month, 1, 0, 0, 0, DateTimeKind.Utc);
        var soonEnd = todayStart.AddDays(7);

        var usageQuery = context.PunchCardUsageRecords
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId && item.StoreId == storeId);

        if (templateId.HasValue)
        {
            usageQuery = usageQuery.Where(item => item.PunchCardTemplateId == templateId.Value);
        }

        var todayUsedCount = await usageQuery
            .Where(item => item.UsedAt >= todayStart && item.UsedAt < todayStart.AddDays(1))
            .CountAsync(cancellationToken);

        var monthUsedCount = await usageQuery
            .Where(item => item.UsedAt >= monthStart && item.UsedAt < monthStart.AddMonths(1))
            .CountAsync(cancellationToken);

        var instanceQuery = context.PunchCardInstances
            .AsNoTracking()
            .Where(item =>
                item.TenantId == tenantId &&
                item.StoreId == storeId &&
                item.Status == PunchCardInstanceStatus.Active &&
                item.RemainingTimes > 0 &&
                item.ExpiresAt.HasValue &&
                item.ExpiresAt.Value >= todayStart &&
                item.ExpiresAt.Value < soonEnd);

        if (templateId.HasValue)
        {
            instanceQuery = instanceQuery.Where(item => item.PunchCardTemplateId == templateId.Value);
        }

        var expiringSoonCount = await instanceQuery.CountAsync(cancellationToken);

        return new PunchCardUsageStatsSnapshot
        {
            TodayUsedCount = todayUsedCount,
            MonthUsedCount = monthUsedCount,
            ExpiringSoonCount = expiringSoonCount
        };
    }

    /// <inheritdoc />
    public Task AddUsageRecordAsync(PunchCardUsageRecord entity, CancellationToken cancellationToken = default)
    {
        return context.PunchCardUsageRecords.AddAsync(entity, cancellationToken).AsTask();
    }

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

    private IQueryable<PunchCardUsageRecord> BuildUsageRecordQuery(
        long tenantId,
        long storeId,
        long? templateId,
        string? keyword,
        PunchCardUsageRecordFilterStatus? status,
        DateTime nowUtc)
    {
        var query = context.PunchCardUsageRecords
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId && item.StoreId == storeId);

        if (templateId.HasValue)
        {
            query = query.Where(item => item.PunchCardTemplateId == templateId.Value);
        }

        var instanceQuery = context.PunchCardInstances
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId && item.StoreId == storeId);

        if (templateId.HasValue)
        {
            instanceQuery = instanceQuery.Where(item => item.PunchCardTemplateId == templateId.Value);
        }

        var utcNow = NormalizeUtc(nowUtc);
        if (status.HasValue)
        {
            instanceQuery = status.Value switch
            {
                PunchCardUsageRecordFilterStatus.Normal => instanceQuery.Where(item =>
                    item.Status == PunchCardInstanceStatus.Active &&
                    item.RemainingTimes > 0 &&
                    (!item.ExpiresAt.HasValue || item.ExpiresAt.Value >= utcNow)),
                PunchCardUsageRecordFilterStatus.UsedUp => instanceQuery.Where(item =>
                    item.Status == PunchCardInstanceStatus.UsedUp ||
                    item.RemainingTimes <= 0),
                PunchCardUsageRecordFilterStatus.Expired => instanceQuery.Where(item =>
                    (item.Status == PunchCardInstanceStatus.Expired ||
                     (item.ExpiresAt.HasValue && item.ExpiresAt.Value < utcNow)) &&
                    item.Status != PunchCardInstanceStatus.Refunded),
                _ => instanceQuery
            };
        }

        if (!string.IsNullOrWhiteSpace(keyword))
        {
            var normalizedKeyword = $"%{keyword.Trim()}%";
            var matchedInstanceIds = context.PunchCardInstances
                .AsNoTracking()
                .Where(item =>
                    item.TenantId == tenantId &&
                    item.StoreId == storeId &&
                    (EF.Functions.ILike(item.MemberName, normalizedKeyword) ||
                     EF.Functions.ILike(item.MemberPhoneMasked, normalizedKeyword) ||
                     EF.Functions.ILike(item.InstanceNo, normalizedKeyword)))
                .Select(item => item.Id);

            query = query.Where(item =>
                EF.Functions.ILike(item.RecordNo, normalizedKeyword) ||
                EF.Functions.ILike(item.ProductName, normalizedKeyword) ||
                matchedInstanceIds.Contains(item.PunchCardInstanceId));
        }

        if (status.HasValue)
        {
            var filteredInstanceIds = instanceQuery.Select(item => item.Id);
            query = query.Where(item => filteredInstanceIds.Contains(item.PunchCardInstanceId));
        }

        return query;
    }

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