using Microsoft.EntityFrameworkCore;
using System.Text.Json;
using TakeoutSaaS.Domain.Membership.Entities;
using TakeoutSaaS.Domain.Membership.Enums;
using TakeoutSaaS.Domain.Membership.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;

namespace TakeoutSaaS.Infrastructure.App.Repositories;

/// <summary>
/// EF 会员消息触达仓储实现。
/// </summary>
public sealed class EfMemberMessageReachRepository(TakeoutAppDbContext context) : IMemberMessageReachRepository
{
    /// <inheritdoc />
    public async Task<(IReadOnlyList<MemberReachMessage> Items, int Total)> SearchMessagesAsync(
        long tenantId,
        MemberMessageStatus? status,
        MemberMessageChannel? channel,
        string? keyword,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        var query = context.MemberReachMessages
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId);

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

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

        var source = await query
            .OrderByDescending(item => item.CreatedAt)
            .ThenByDescending(item => item.Id)
            .ToListAsync(cancellationToken);

        if (channel.HasValue)
        {
            source = source
                .Where(item => HasChannel(item.ChannelsJson, channel.Value))
                .ToList();
        }

        var total = source.Count;
        var items = source
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToList();

        return (items, total);
    }

    /// <inheritdoc />
    public Task<MemberReachMessage?> FindMessageByIdAsync(long tenantId, long messageId, CancellationToken cancellationToken = default)
    {
        return context.MemberReachMessages
            .FirstOrDefaultAsync(item => item.TenantId == tenantId && item.Id == messageId, cancellationToken);
    }

    /// <inheritdoc />
    public Task AddMessageAsync(MemberReachMessage message, CancellationToken cancellationToken = default)
    {
        return context.MemberReachMessages.AddAsync(message, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task UpdateMessageAsync(MemberReachMessage message, CancellationToken cancellationToken = default)
    {
        context.MemberReachMessages.Update(message);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task DeleteMessageAsync(MemberReachMessage message, CancellationToken cancellationToken = default)
    {
        context.MemberReachMessages.Remove(message);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<MemberReachRecipient>> GetRecipientsAsync(
        long tenantId,
        long messageId,
        CancellationToken cancellationToken = default)
    {
        return await context.MemberReachRecipients
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId && item.MessageId == messageId)
            .OrderBy(item => item.MemberId)
            .ThenBy(item => item.Channel)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task RemoveRecipientsAsync(long tenantId, long messageId, CancellationToken cancellationToken = default)
    {
        var recipients = await context.MemberReachRecipients
            .Where(item => item.TenantId == tenantId && item.MessageId == messageId)
            .ToListAsync(cancellationToken);

        if (recipients.Count == 0)
        {
            return;
        }

        context.MemberReachRecipients.RemoveRange(recipients);
    }

    /// <inheritdoc />
    public async Task AddRecipientsAsync(
        IReadOnlyCollection<MemberReachRecipient> recipients,
        CancellationToken cancellationToken = default)
    {
        if (recipients.Count == 0)
        {
            return;
        }

        await context.MemberReachRecipients.AddRangeAsync(recipients, cancellationToken);
    }

    /// <inheritdoc />
    public async Task<(IReadOnlyList<MemberMessageTemplate> Items, int Total)> SearchTemplatesAsync(
        long tenantId,
        MemberMessageTemplateCategory? category,
        string? keyword,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        var query = context.MemberMessageTemplates
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId);

        if (category.HasValue)
        {
            query = query.Where(item => item.Category == category.Value);
        }

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

        var total = await query.CountAsync(cancellationToken);
        var items = await query
            .OrderByDescending(item => item.UsageCount)
            .ThenByDescending(item => item.UpdatedAt ?? item.CreatedAt)
            .ThenByDescending(item => item.Id)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync(cancellationToken);

        return (items, total);
    }

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

    /// <inheritdoc />
    public Task<MemberMessageTemplate?> FindTemplateByNameAsync(long tenantId, string name, CancellationToken cancellationToken = default)
    {
        var normalizedName = (name ?? string.Empty).Trim();
        if (string.IsNullOrWhiteSpace(normalizedName))
        {
            return Task.FromResult<MemberMessageTemplate?>(null);
        }

        return context.MemberMessageTemplates
            .FirstOrDefaultAsync(
                item =>
                    item.TenantId == tenantId &&
                    EF.Functions.ILike(item.Name, normalizedName),
                cancellationToken);
    }

    /// <inheritdoc />
    public Task AddTemplateAsync(MemberMessageTemplate template, CancellationToken cancellationToken = default)
    {
        return context.MemberMessageTemplates.AddAsync(template, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task UpdateTemplateAsync(MemberMessageTemplate template, CancellationToken cancellationToken = default)
    {
        context.MemberMessageTemplates.Update(template);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task DeleteTemplateAsync(MemberMessageTemplate template, CancellationToken cancellationToken = default)
    {
        context.MemberMessageTemplates.Remove(template);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public async Task<MemberMessageMonthlyStatsSnapshot> GetMonthlyStatsAsync(
        long tenantId,
        DateTime monthStartUtc,
        DateTime monthEndUtc,
        CancellationToken cancellationToken = default)
    {
        var sentMessageCount = await context.MemberReachMessages
            .AsNoTracking()
            .Where(item =>
                item.TenantId == tenantId &&
                item.Status == MemberMessageStatus.Sent &&
                item.SentAt.HasValue &&
                item.SentAt.Value >= monthStartUtc &&
                item.SentAt.Value < monthEndUtc)
            .CountAsync(cancellationToken);

        var recipients = await context.MemberReachRecipients
            .AsNoTracking()
            .Where(item =>
                item.TenantId == tenantId &&
                item.SentAt.HasValue &&
                item.SentAt.Value >= monthStartUtc &&
                item.SentAt.Value < monthEndUtc)
            .Select(item => new
            {
                item.MemberId,
                item.Status,
                item.ReadAt,
                item.ConvertedAt
            })
            .ToListAsync(cancellationToken);

        var reachMemberCount = recipients
            .Where(item => item.Status == MemberMessageRecipientStatus.Sent)
            .Select(item => item.MemberId)
            .Distinct()
            .Count();
        var sentRecipientCount = recipients.Count(item => item.Status == MemberMessageRecipientStatus.Sent);
        var readRecipientCount = recipients.Count(item => item.ReadAt.HasValue);
        var convertedRecipientCount = recipients.Count(item => item.ConvertedAt.HasValue);

        return new MemberMessageMonthlyStatsSnapshot(
            sentMessageCount,
            reachMemberCount,
            sentRecipientCount,
            readRecipientCount,
            convertedRecipientCount);
    }

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

    private static bool HasChannel(string channelsJson, MemberMessageChannel channel)
    {
        if (string.IsNullOrWhiteSpace(channelsJson))
        {
            return false;
        }

        try
        {
            var channels = JsonSerializer.Deserialize<List<string>>(channelsJson) ?? [];
            var target = channel switch
            {
                MemberMessageChannel.InApp => "inapp",
                MemberMessageChannel.Sms => "sms",
                MemberMessageChannel.WeChatMini => "wechat-mini",
                _ => string.Empty
            };

            if (string.IsNullOrWhiteSpace(target))
            {
                return false;
            }

            return channels.Any(item => string.Equals(item, target, StringComparison.OrdinalIgnoreCase));
        }
        catch
        {
            return false;
        }
    }
}
