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

namespace TakeoutSaaS.Application.App.Tenants.Handlers;

/// <summary>
/// 查询套餐当前使用租户列表处理器。
/// </summary>
public sealed class GetTenantPackageTenantsQueryHandler(IDapperExecutor dapperExecutor)
    : IRequestHandler<GetTenantPackageTenantsQuery, PagedResult<TenantPackageTenantDto>>
{
    /// <inheritdoc />
    public async Task<PagedResult<TenantPackageTenantDto>> Handle(GetTenantPackageTenantsQuery request, CancellationToken cancellationToken)
    {
        // 1. 参数规范化
        var page = request.Page <= 0 ? 1 : request.Page;
        var pageSize = request.PageSize <= 0 ? 20 : request.PageSize;
        var keyword = string.IsNullOrWhiteSpace(request.Keyword) ? null : request.Keyword.Trim();

        // 2. (空行后) 以当前时间为准筛选“有效订阅”
        var now = DateTime.UtcNow;
        var expiringDays = request.ExpiringWithinDays is > 0 ? request.ExpiringWithinDays : null;
        var expiryEnd = expiringDays.HasValue ? now.AddDays(expiringDays.Value) : (DateTime?)null;
        var offset = (page - 1) * pageSize;

        // 3. (空行后) 查询总数 + 列表
        return await dapperExecutor.QueryAsync(
            DatabaseConstants.AppDataSource,
            DatabaseConnectionRole.Read,
            async (connection, token) =>
            {
                // 3.1 统计总数
                var total = await ExecuteScalarIntAsync(
                    connection,
                    BuildCountSql(),
                    [
                        ("packageId", request.TenantPackageId),
                        ("now", now),
                        ("expiryEnd", expiryEnd),
                        ("keyword", keyword)
                    ],
                    token);

                // 3.2 (空行后) 查询列表
                var listSql = BuildListSql(expiryEnd.HasValue);
                await using var listCommand = CreateCommand(
                    connection,
                    listSql,
                    [
                        ("packageId", request.TenantPackageId),
                        ("now", now),
                        ("expiryEnd", expiryEnd),
                        ("keyword", keyword),
                        ("offset", offset),
                        ("limit", pageSize)
                    ]);

                await using var reader = await listCommand.ExecuteReaderAsync(token);
                var items = new List<TenantPackageTenantDto>();
                while (await reader.ReadAsync(token))
                {
                    items.Add(new TenantPackageTenantDto
                    {
                        TenantId = reader.GetInt64(0),
                        Code = reader.GetString(1),
                        Name = reader.GetString(2),
                        Status = (TenantStatus)reader.GetInt32(3),
                        ContactName = reader.IsDBNull(4) ? null : reader.GetString(4),
                        ContactPhone = reader.IsDBNull(5) ? null : reader.GetString(5),
                        SubscriptionEffectiveFrom = reader.GetDateTime(6),
                        SubscriptionEffectiveTo = reader.GetDateTime(7)
                    });
                }

                // 3.3 (空行后) 返回分页
                return new PagedResult<TenantPackageTenantDto>(items, page, pageSize, total);
            },
            cancellationToken);
    }

    private static string BuildCountSql()
    {
        return """
            select count(*)
            from public.tenants t
            where t."DeletedAt" is null
              and (
                @keyword::text is null
                or t."Name" ilike ('%' || @keyword::text || '%')
                or t."Code" ilike ('%' || @keyword::text || '%')
                or coalesce(t."ContactName", '') ilike ('%' || @keyword::text || '%')
                or coalesce(t."ContactPhone", '') ilike ('%' || @keyword::text || '%')
              )
              and exists (
                select 1
                from public.tenant_subscriptions s
                where s."DeletedAt" is null
                  and s."TenantId" = t."Id"
                  and s."TenantPackageId" = @packageId
                  and s."Status" = 1
                  and s."EffectiveFrom" <= @now
                  and s."EffectiveTo" >= @now
                  and (
                    @expiryEnd::timestamp with time zone is null
                    or s."EffectiveTo" <= @expiryEnd
                  )
              );
            """;
    }

    private static string BuildListSql(bool orderByExpiryAsc)
    {
        if (orderByExpiryAsc)
        {
            return """
                select
                  t."Id" as "TenantId",
                  t."Code",
                  t."Name",
                  t."Status",
                  t."ContactName",
                  t."ContactPhone",
                  s."EffectiveFrom",
                  s."EffectiveTo"
                from public.tenants t
                join lateral (
                  select s."EffectiveFrom", s."EffectiveTo"
                  from public.tenant_subscriptions s
                  where s."DeletedAt" is null
                    and s."TenantId" = t."Id"
                    and s."TenantPackageId" = @packageId
                    and s."Status" = 1
                    and s."EffectiveFrom" <= @now
                    and s."EffectiveTo" >= @now
                    and (
                      @expiryEnd::timestamp with time zone is null
                      or s."EffectiveTo" <= @expiryEnd
                    )
                  order by s."EffectiveTo" asc
                  limit 1
                ) s on true
                where t."DeletedAt" is null
                  and (
                    @keyword::text is null
                    or t."Name" ilike ('%' || @keyword::text || '%')
                    or t."Code" ilike ('%' || @keyword::text || '%')
                    or coalesce(t."ContactName", '') ilike ('%' || @keyword::text || '%')
                    or coalesce(t."ContactPhone", '') ilike ('%' || @keyword::text || '%')
                  )
                order by s."EffectiveTo" asc
                offset @offset
                limit @limit;
                """;
        }

        return """
            select
              t."Id" as "TenantId",
              t."Code",
              t."Name",
              t."Status",
              t."ContactName",
              t."ContactPhone",
              s."EffectiveFrom",
              s."EffectiveTo"
            from public.tenants t
            join lateral (
              select s."EffectiveFrom", s."EffectiveTo"
              from public.tenant_subscriptions s
              where s."DeletedAt" is null
                and s."TenantId" = t."Id"
                and s."TenantPackageId" = @packageId
                and s."Status" = 1
                and s."EffectiveFrom" <= @now
                and s."EffectiveTo" >= @now
                and (
                  @expiryEnd::timestamp with time zone is null
                  or s."EffectiveTo" <= @expiryEnd
                )
              order by s."EffectiveTo" desc
              limit 1
            ) s on true
            where t."DeletedAt" is null
              and (
                @keyword::text is null
                or t."Name" ilike ('%' || @keyword::text || '%')
                or t."Code" ilike ('%' || @keyword::text || '%')
                or coalesce(t."ContactName", '') ilike ('%' || @keyword::text || '%')
                or coalesce(t."ContactPhone", '') ilike ('%' || @keyword::text || '%')
              )
            order by t."CreatedAt" desc
            offset @offset
            limit @limit;
            """;
    }

    private static async Task<int> ExecuteScalarIntAsync(
        IDbConnection connection,
        string sql,
        (string Name, object? Value)[] parameters,
        CancellationToken cancellationToken)
    {
        await using var command = CreateCommand(connection, sql, parameters);
        var result = await command.ExecuteScalarAsync(cancellationToken);
        return result is null or DBNull ? 0 : Convert.ToInt32(result);
    }

    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;
    }
}
