using MediatR;
using System.Data;
using System.Data.Common;
using TakeoutSaaS.Application.App.Billings.Dto;
using TakeoutSaaS.Application.App.Billings.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.Billings.Handlers;

/// <summary>
/// 查询逾期账单列表处理器。
/// </summary>
public sealed class GetOverdueBillingsQueryHandler(
    IDapperExecutor dapperExecutor)
    : IRequestHandler<GetOverdueBillingsQuery, PagedResult<BillingListDto>>
{
    /// <summary>
    /// 处理查询逾期账单列表请求。
    /// </summary>
    /// <param name="request">查询命令。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>分页逾期账单列表 DTO。</returns>
    public async Task<PagedResult<BillingListDto>> Handle(GetOverdueBillingsQuery request, CancellationToken cancellationToken)
    {
        // 1. 参数规范化
        var page = request.PageNumber <= 0 ? 1 : request.PageNumber;
        var pageSize = request.PageSize is <= 0 or > 200 ? 20 : request.PageSize;
        var offset = (page - 1) * pageSize;
        var now = DateTime.UtcNow;

        // 2. 查询总数 + 列表
        return await dapperExecutor.QueryAsync(
            DatabaseConstants.AppDataSource,
            DatabaseConnectionRole.Read,
            async (connection, token) =>
            {
                // 2.1 统计总数
                var total = await ExecuteScalarIntAsync(
                    connection,
                    BuildCountSql(),
                    [
                        ("now", now)
                    ],
                    token);

                // 2.2 查询列表
                await using var listCommand = CreateCommand(
                    connection,
                    BuildListSql(),
                    [
                        ("now", now),
                        ("offset", offset),
                        ("limit", pageSize)
                    ]);

                await using var reader = await listCommand.ExecuteReaderAsync(token);
                var items = new List<BillingListDto>();
                while (await reader.ReadAsync(token))
                {
                    var dueDate = reader.GetDateTime(13);
                    var status = (TenantBillingStatus)reader.GetInt32(12);
                    var amountDue = reader.GetDecimal(8);
                    var discountAmount = reader.GetDecimal(9);
                    var taxAmount = reader.GetDecimal(10);
                    var totalAmount = amountDue - discountAmount + taxAmount;
                    var overdueDays = dueDate < now ? (int)(now - dueDate).TotalDays : 0;

                    items.Add(new BillingListDto
                    {
                        Id = reader.GetInt64(0),
                        TenantId = reader.GetInt64(1),
                        TenantName = reader.IsDBNull(2) ? string.Empty : reader.GetString(2),
                        SubscriptionId = reader.IsDBNull(3) ? null : reader.GetInt64(3),
                        StatementNo = reader.GetString(4),
                        BillingType = (BillingType)reader.GetInt32(5),
                        PeriodStart = reader.GetDateTime(6),
                        PeriodEnd = reader.GetDateTime(7),
                        AmountDue = amountDue,
                        DiscountAmount = discountAmount,
                        TaxAmount = taxAmount,
                        TotalAmount = totalAmount,
                        AmountPaid = reader.GetDecimal(11),
                        Status = status,
                        DueDate = dueDate,
                        Currency = reader.IsDBNull(14) ? "CNY" : reader.GetString(14),
                        IsOverdue = true,
                        OverdueDays = overdueDays,
                        CreatedAt = reader.GetDateTime(15),
                        UpdatedAt = reader.IsDBNull(16) ? null : reader.GetDateTime(16)
                    });
                }

                // 2.3 返回分页
                return new PagedResult<BillingListDto>(items, page, pageSize, total);
            },
            cancellationToken);
    }

    private static string BuildCountSql()
    {
        return """
            select count(*)
            from public.tenant_billing_statements b
            join public.tenants t on t."Id" = b."TenantId" and t."DeletedAt" is null
            where b."DeletedAt" is null
              and b."DueDate" < @now
              and b."Status" in (0, 2);
            """;
    }

    private static string BuildListSql()
    {
        return """
            select
              b."Id",
              b."TenantId",
              t."Name" as "TenantName",
              b."SubscriptionId",
              b."StatementNo",
              b."BillingType",
              b."PeriodStart",
              b."PeriodEnd",
              b."AmountDue",
              b."DiscountAmount",
              b."TaxAmount",
              b."AmountPaid",
              b."Status",
              b."DueDate",
              b."Currency",
              b."CreatedAt",
              b."UpdatedAt"
            from public.tenant_billing_statements b
            join public.tenants t on t."Id" = b."TenantId" and t."DeletedAt" is null
            where b."DeletedAt" is null
              and b."DueDate" < @now
              and b."Status" in (0, 2)
            order by b."DueDate" asc
            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;
    }
}
