using MediatR;
using System.Data;
using System.Data.Common;
using TakeoutSaaS.Application.App.Tenants.Dto;
using TakeoutSaaS.Application.App.Tenants.Queries;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Data;

namespace TakeoutSaaS.Application.App.Tenants.Handlers;

/// <summary>
/// 查询套餐使用统计处理器。
/// </summary>
public sealed class GetTenantPackageUsagesQueryHandler(IDapperExecutor dapperExecutor)
    : IRequestHandler<GetTenantPackageUsagesQuery, IReadOnlyList<TenantPackageUsageDto>>
{
    /// <inheritdoc />
    public async Task<IReadOnlyList<TenantPackageUsageDto>> Handle(GetTenantPackageUsagesQuery request, CancellationToken cancellationToken)
    {
        // 1. 规范化输入
        var ids = request.TenantPackageIds?
            .Where(x => x > 0)
            .Distinct()
            .ToArray();

        // 2. (空行后) 构造 SQL（以当前时间为准统计“有效订阅/使用租户/到期分布”）
        var now = DateTime.UtcNow;
        var date7 = now.AddDays(7);
        var date15 = now.AddDays(15);
        var date30 = now.AddDays(30);
        var sql = BuildSql(ids, out var parameters, now, date7, date15, date30);

        // 3. (空行后) 查询统计结果
        return await dapperExecutor.QueryAsync(
            DatabaseConstants.AppDataSource,
            DatabaseConnectionRole.Read,
            async (connection, token) =>
            {
                await using var command = CreateCommand(connection, sql, parameters);
                await using var reader = await command.ExecuteReaderAsync(token);
                var list = new List<TenantPackageUsageDto>();

                // 4. (空行后) 逐行读取
                while (await reader.ReadAsync(token))
                {
                    list.Add(new TenantPackageUsageDto
                    {
                        TenantPackageId = reader.GetInt64(0),
                        ActiveSubscriptionCount = reader.GetInt32(1),
                        ActiveTenantCount = reader.GetInt32(2),
                        TotalSubscriptionCount = reader.GetInt32(3),
                        Mrr = reader.IsDBNull(4) ? 0m : reader.GetDecimal(4),
                        Arr = reader.IsDBNull(5) ? 0m : reader.GetDecimal(5),
                        ExpiringTenantCount7Days = reader.IsDBNull(6) ? 0 : reader.GetInt32(6),
                        ExpiringTenantCount15Days = reader.IsDBNull(7) ? 0 : reader.GetInt32(7),
                        ExpiringTenantCount30Days = reader.IsDBNull(8) ? 0 : reader.GetInt32(8)
                    });
                }

                return (IReadOnlyList<TenantPackageUsageDto>)list;
            },
            cancellationToken);
    }

    private static string BuildSql(
        long[]? ids,
        out (string Name, object? Value)[] parameters,
        DateTime now,
        DateTime date7,
        DateTime date15,
        DateTime date30)
    {
        // 1. 基础查询：先按订阅表聚合，再回连套餐表计算 MRR/ARR
        var builder = new System.Text.StringBuilder();
        builder.AppendLine("""
            with stats as (
              select
                "TenantPackageId" as "TenantPackageId",
                count(*) filter (where "Status" = 1 and "EffectiveFrom" <= @now and "EffectiveTo" >= @now) as "ActiveSubscriptionCount",
                count(distinct "TenantId") filter (where "Status" = 1 and "EffectiveFrom" <= @now and "EffectiveTo" >= @now) as "ActiveTenantCount",
                count(distinct "TenantId") filter (where "Status" = 1 and "EffectiveFrom" <= @now and "EffectiveTo" >= @now and "EffectiveTo" <= @date7) as "ExpiringTenantCount7Days",
                count(distinct "TenantId") filter (where "Status" = 1 and "EffectiveFrom" <= @now and "EffectiveTo" >= @now and "EffectiveTo" <= @date15) as "ExpiringTenantCount15Days",
                count(distinct "TenantId") filter (where "Status" = 1 and "EffectiveFrom" <= @now and "EffectiveTo" >= @now and "EffectiveTo" <= @date30) as "ExpiringTenantCount30Days",
                count(*) as "TotalSubscriptionCount"
              from public.tenant_subscriptions
              where "DeletedAt" is null
            """);

        var list = new List<(string Name, object? Value)>
        {
            ("now", now),
            ("date7", date7),
            ("date15", date15),
            ("date30", date30)
        };

        // 2. (空行后) 可选按套餐 ID 过滤
        if (ids is { Length: > 0 })
        {
            builder.Append("  and \"TenantPackageId\" in (");
            for (var i = 0; i < ids.Length; i++)
            {
                if (i > 0)
                {
                    builder.Append(',');
                }

                var name = $"p{i}";
                builder.Append($"@{name}");
                list.Add((name, ids[i]));
            }

            builder.AppendLine(")");
        }

        // 3. (空行后) 分组与回连套餐表
        builder.AppendLine("""
              group by "TenantPackageId"
            )
            select
              s."TenantPackageId" as "TenantPackageId",
              s."ActiveSubscriptionCount" as "ActiveSubscriptionCount",
              s."ActiveTenantCount" as "ActiveTenantCount",
              s."TotalSubscriptionCount" as "TotalSubscriptionCount",
              (s."ActiveSubscriptionCount"::numeric * coalesce(p."MonthlyPrice", (p."YearlyPrice" / 12.0), 0))::numeric(18, 2) as "Mrr",
              (s."ActiveSubscriptionCount"::numeric * coalesce(p."YearlyPrice", (p."MonthlyPrice" * 12), 0))::numeric(18, 2) as "Arr",
              s."ExpiringTenantCount7Days" as "ExpiringTenantCount7Days",
              s."ExpiringTenantCount15Days" as "ExpiringTenantCount15Days",
              s."ExpiringTenantCount30Days" as "ExpiringTenantCount30Days"
            from stats s
            left join public.tenant_packages p on p."Id" = s."TenantPackageId" and p."DeletedAt" is null;
            """);

        parameters = list.ToArray();
        return builder.ToString();
    }

    private static DbCommand CreateCommand(IDbConnection connection, string sql, (string Name, object? Value)[] parameters)
    {
        var command = connection.CreateCommand();
        command.CommandText = sql;

        foreach (var (name, value) in parameters)
        {
            var p = command.CreateParameter();
            p.ParameterName = name;
            p.Value = value ?? DBNull.Value;
            command.Parameters.Add(p);
        }

        return (DbCommand)command;
    }
}
