using MediatR;
using System.Data;
using System.Data.Common;
using System.Text.Json;
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.Exceptions;

namespace TakeoutSaaS.Application.App.Billings.Handlers;

/// <summary>
/// 查询账单详情处理器。
/// </summary>
public sealed class GetBillingDetailQueryHandler(
    IDapperExecutor dapperExecutor)
    : IRequestHandler<GetBillingDetailQuery, BillingDetailDto>
{
    /// <summary>
    /// 处理查询账单详情请求。
    /// </summary>
    /// <param name="request">查询命令。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>账单详情 DTO。</returns>
    public async Task<BillingDetailDto> Handle(GetBillingDetailQuery request, CancellationToken cancellationToken)
    {
        // 1. 查询账单 + 支付记录（同一连接，避免多次往返）
        return await dapperExecutor.QueryAsync(
            DatabaseConstants.AppDataSource,
            DatabaseConnectionRole.Read,
            async (connection, token) =>
            {
                // 1.1 查询账单
                await using var billCommand = CreateCommand(
                    connection,
                    BuildBillingSql(),
                    [
                        ("billingId", request.BillingId)
                    ]);

                await using var billReader = await billCommand.ExecuteReaderAsync(token);
                if (!await billReader.ReadAsync(token))
                {
                    throw new BusinessException(ErrorCodes.NotFound, "账单不存在");
                }

                // 1.2 读取账单行数据到内存（释放 Reader，避免同连接并发执行命令）
                var billingId = billReader.GetInt64(0);
                var tenantId = billReader.GetInt64(1);
                var tenantName = billReader.IsDBNull(2) ? string.Empty : billReader.GetString(2);
                long? subscriptionId = billReader.IsDBNull(3) ? null : billReader.GetInt64(3);
                var statementNo = billReader.GetString(4);
                var billingType = (BillingType)billReader.GetInt32(5);
                var status = (TenantBillingStatus)billReader.GetInt32(6);
                var periodStart = billReader.GetDateTime(7);
                var periodEnd = billReader.GetDateTime(8);
                var amountDue = billReader.GetDecimal(9);
                var discountAmount = billReader.GetDecimal(10);
                var taxAmount = billReader.GetDecimal(11);
                var amountPaid = billReader.GetDecimal(12);
                var currency = billReader.IsDBNull(13) ? "CNY" : billReader.GetString(13);
                var dueDate = billReader.GetDateTime(14);
                DateTime? reminderSentAt = billReader.IsDBNull(15) ? null : billReader.GetDateTime(15);
                DateTime? overdueNotifiedAt = billReader.IsDBNull(16) ? null : billReader.GetDateTime(16);
                var notes = billReader.IsDBNull(17) ? null : billReader.GetString(17);
                var lineItemsJson = billReader.IsDBNull(18) ? null : billReader.GetString(18);
                var createdAt = billReader.GetDateTime(19);
                long? createdBy = billReader.IsDBNull(20) ? null : billReader.GetInt64(20);
                DateTime? updatedAt = billReader.IsDBNull(21) ? null : billReader.GetDateTime(21);
                long? updatedBy = billReader.IsDBNull(22) ? null : billReader.GetInt64(22);

                // 1.3 主动释放账单 Reader，确保后续查询不会触发 Npgsql 并发命令异常
                await billReader.DisposeAsync();

                // 1.4 反序列化账单明细
                var lineItems = new List<BillingLineItemDto>();
                if (!string.IsNullOrWhiteSpace(lineItemsJson))
                {
                    try
                    {
                        lineItems = JsonSerializer.Deserialize<List<BillingLineItemDto>>(lineItemsJson) ?? [];
                    }
                    catch
                    {
                        lineItems = [];
                    }
                }

                // 1.5 查询支付记录
                var payments = new List<PaymentRecordDto>();
                await using var paymentCommand = CreateCommand(
                    connection,
                    BuildPaymentsSql(),
                    [
                        ("billingId", request.BillingId)
                    ]);

                await using var paymentReader = await paymentCommand.ExecuteReaderAsync(token);
                while (await paymentReader.ReadAsync(token))
                {
                    payments.Add(new PaymentRecordDto
                    {
                        Id = paymentReader.GetInt64(0),
                        TenantId = paymentReader.GetInt64(1),
                        BillingId = paymentReader.GetInt64(2),
                        Amount = paymentReader.GetDecimal(3),
                        Method = (TenantPaymentMethod)paymentReader.GetInt32(4),
                        Status = (TenantPaymentStatus)paymentReader.GetInt32(5),
                        TransactionNo = paymentReader.IsDBNull(6) ? null : paymentReader.GetString(6),
                        ProofUrl = paymentReader.IsDBNull(7) ? null : paymentReader.GetString(7),
                        Notes = paymentReader.IsDBNull(8) ? null : paymentReader.GetString(8),
                        VerifiedBy = paymentReader.IsDBNull(9) ? null : paymentReader.GetInt64(9),
                        VerifiedAt = paymentReader.IsDBNull(10) ? null : paymentReader.GetDateTime(10),
                        RefundReason = paymentReader.IsDBNull(11) ? null : paymentReader.GetString(11),
                        RefundedAt = paymentReader.IsDBNull(12) ? null : paymentReader.GetDateTime(12),
                        PaidAt = paymentReader.IsDBNull(13) ? null : paymentReader.GetDateTime(13),
                        IsVerified = !paymentReader.IsDBNull(10),
                        CreatedAt = paymentReader.GetDateTime(14)
                    });
                }

                // 1.6 组装详情 DTO
                var totalAmount = amountDue - discountAmount + taxAmount;

                return new BillingDetailDto
                {
                    Id = billingId,
                    TenantId = tenantId,
                    TenantName = tenantName,
                    SubscriptionId = subscriptionId,
                    StatementNo = statementNo,
                    BillingType = billingType,
                    Status = status,
                    PeriodStart = periodStart,
                    PeriodEnd = periodEnd,
                    AmountDue = amountDue,
                    DiscountAmount = discountAmount,
                    TaxAmount = taxAmount,
                    TotalAmount = totalAmount,
                    AmountPaid = amountPaid,
                    Currency = currency,
                    DueDate = dueDate,
                    ReminderSentAt = reminderSentAt,
                    OverdueNotifiedAt = overdueNotifiedAt,
                    LineItemsJson = lineItemsJson,
                    LineItems = lineItems,
                    Payments = payments,
                    Notes = notes,
                    CreatedAt = createdAt,
                    CreatedBy = createdBy,
                    UpdatedAt = updatedAt,
                    UpdatedBy = updatedBy
                };
            },
            cancellationToken);
    }

    private static string BuildBillingSql()
    {
        return """
            select
              b."Id",
              b."TenantId",
              t."Name" as "TenantName",
              b."SubscriptionId",
              b."StatementNo",
              b."BillingType",
              b."Status",
              b."PeriodStart",
              b."PeriodEnd",
              b."AmountDue",
              b."DiscountAmount",
              b."TaxAmount",
              b."AmountPaid",
              b."Currency",
              b."DueDate",
              b."ReminderSentAt",
              b."OverdueNotifiedAt",
              b."Notes",
              b."LineItemsJson",
              b."CreatedAt",
              b."CreatedBy",
              b."UpdatedAt",
              b."UpdatedBy"
            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."Id" = @billingId
            limit 1;
            """;
    }

    private static string BuildPaymentsSql()
    {
        return """
            select
              p."Id",
              p."TenantId",
              p."BillingStatementId",
              p."Amount",
              p."Method",
              p."Status",
              p."TransactionNo",
              p."ProofUrl",
              p."Notes",
              p."VerifiedBy",
              p."VerifiedAt",
              p."RefundReason",
              p."RefundedAt",
              p."PaidAt",
              p."CreatedAt"
            from public.tenant_payments p
            where p."DeletedAt" is null
              and p."BillingStatementId" = @billingId
            order by p."CreatedAt" desc;
            """;
    }

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