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

namespace Tiku.Infrastructure.Growth;

public sealed partial class ReferralService
{
    public async Task<ReferralStatsItem> GetStatsAsync(
        ReferralAdminActor actor,
        ReferralStatsQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var referrerUserId = query.ReferrerUserId ?? actor.UserId;
        await AssertActiveMemberAsync(actor.TenantId, referrerUserId, cancellationToken);
        return await BuildStatsAsync(actor.TenantId, referrerUserId, cancellationToken);
    }

    public async Task<ReferralList<ReferralStatsItem>> GetSalesStatsAsync(
        ReferralAdminActor actor,
        ReferralStatsQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var limit = Math.Clamp(query.Limit ?? 100, 1, 500);
        var referrerIdsQuery = dbContext.ReferralLeads
            .AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId && item.ReferrerUserId != null);
        if (query.ReferrerUserId.HasValue)
            referrerIdsQuery = referrerIdsQuery.Where(item => item.ReferrerUserId == query.ReferrerUserId.Value);

        var referrerIds = await referrerIdsQuery
            .Select(item => item.ReferrerUserId!.Value)
            .Distinct()
            .Take(limit)
            .ToArrayAsync(cancellationToken);
        var stats = new List<ReferralStatsItem>(referrerIds.Length);
        foreach (var referrerId in referrerIds)
            stats.Add(await BuildStatsAsync(actor.TenantId, referrerId, cancellationToken));

        return new ReferralList<ReferralStatsItem>(
            stats
                .OrderByDescending(item => item.PaidAmountCents)
                .ThenByDescending(item => item.LeadCount)
                .Take(limit)
                .ToArray());
    }

    public async Task<ReferralConversionReport> GetConversionReportAsync(
        ReferralAdminActor actor,
        ReferralConversionQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var today = DateOnly.FromDateTime(DateTime.UtcNow);
        var endDate = query.EndDate ?? today;
        var startDate = query.StartDate ?? endDate.AddDays(-Math.Clamp(query.Days ?? 30, 1, 365) + 1);
        if (endDate < startDate) throw new ReferralException("Referral date range was invalid.", "invalid_date_range");

        var start = startDate.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
        var endExclusive = endDate.AddDays(1).ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);
        var leadsQuery = dbContext.ReferralLeads
            .AsNoTracking()
            .Where(item =>
                item.TenantId == actor.TenantId &&
                item.BoundAt >= start &&
                item.BoundAt < endExclusive);
        if (query.ReferrerUserId.HasValue)
            leadsQuery = leadsQuery.Where(item => item.ReferrerUserId == query.ReferrerUserId.Value);

        var leads = await leadsQuery
            .OrderByDescending(item => item.BoundAt)
            .ToArrayAsync(cancellationToken);
        var paidStudentIds = await dbContext.Orders.AsNoTracking()
            .Where(item =>
                item.TenantId == actor.TenantId &&
                item.Status == OrderStatus.Paid &&
                item.UserId != null &&
                item.PaidAt != null &&
                item.PaidAt >= start &&
                item.PaidAt < endExclusive)
            .GroupBy(item => item.UserId!.Value)
            .Select(group => new
            {
                UserId = group.Key,
                AmountCents = group.Sum(item => item.AmountCents)
            })
            .ToDictionaryAsync(item => item.UserId, item => item.AmountCents, cancellationToken);
        var paidLeadCount = leads.Count(item => paidStudentIds.ContainsKey(item.StudentUserId));
        var paidAmountCents = leads.Sum(item => paidStudentIds.GetValueOrDefault(item.StudentUserId));
        var top = await GetSalesStatsAsync(
            actor,
            new ReferralStatsQuery(query.ReferrerUserId, query.Limit ?? 20),
            cancellationToken);

        return new ReferralConversionReport(
            startDate,
            endDate,
            leads.Length,
            paidLeadCount,
            paidAmountCents,
            leads.Length == 0 ? 0m : Math.Round(paidLeadCount * 100m / leads.Length, 2),
            top.Items,
            leads
                .Where(item => !paidStudentIds.ContainsKey(item.StudentUserId))
                .Take(Math.Clamp(query.Limit ?? 20, 1, 100))
                .Select(item => ToLeadItem(item, false))
                .ToArray());
    }

    public async Task<ReferralList<ReferralLeadItem>> GetClientsAsync(
        ReferralAdminActor actor,
        ReferralStatsQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var referrerUserId = query.ReferrerUserId ?? actor.UserId;
        await AssertActiveMemberAsync(actor.TenantId, referrerUserId, cancellationToken);
        var leads = await dbContext.ReferralLeads
            .AsNoTracking()
            .Where(item =>
                item.TenantId == actor.TenantId &&
                item.ReferrerUserId == referrerUserId)
            .OrderByDescending(item => item.BoundAt)
            .Take(Math.Clamp(query.Limit ?? 100, 1, 500))
            .ToArrayAsync(cancellationToken);
        return new ReferralList<ReferralLeadItem>(leads.Select(item => ToLeadItem(item, false)).ToArray());
    }
}