using System.Data;
using System.Data.Common;
using MediatR;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Data;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.App.Stores.Handlers;

/// <summary>
/// 资质预警查询处理器。
/// </summary>
public sealed class ListExpiringStoreQualificationsQueryHandler(
    IDapperExecutor dapperExecutor,
    ITenantProvider tenantProvider)
    : IRequestHandler<ListExpiringStoreQualificationsQuery, StoreQualificationAlertResultDto>
{
    /// <inheritdoc />
    public async Task<StoreQualificationAlertResultDto> Handle(
        ListExpiringStoreQualificationsQuery request,
        CancellationToken cancellationToken)
    {
        // 1. 规范化参数
        var page = request.Page <= 0 ? 1 : request.Page;
        var pageSize = request.PageSize is <= 0 or > 200 ? 20 : request.PageSize;
        var daysThreshold = request.DaysThreshold is null or <= 0 ? 30 : request.DaysThreshold.Value;
        if (daysThreshold > 365)
        {
            daysThreshold = 365;
        }
        var offset = (page - 1) * pageSize;
        var now = DateOnly.FromDateTime(DateTime.UtcNow);
        var expiringBefore = now.AddDays(daysThreshold);

        // 2. (空行后) 读取当前租户并校验跨租户
        var currentTenantId = tenantProvider.GetCurrentTenantId();
        if (currentTenantId <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "缺少租户标识");
        }

        if (request.TenantId.HasValue && request.TenantId.Value != currentTenantId)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "禁止跨租户查询资质预警");
        }
        var tenantId = currentTenantId;

        // 3. (空行后) 执行查询
        return await dapperExecutor.QueryAsync(
            DatabaseConstants.AppDataSource,
            DatabaseConnectionRole.Read,
            async (connection, token) =>
            {
                // 3.1 统计汇总
                var summary = await ExecuteSummaryAsync(connection, now, expiringBefore, tenantId, token);

                // 3.2 (空行后) 统计总数
                var total = await ExecuteScalarIntAsync(
                    connection,
                    BuildCountSql(),
                    [
                        ("tenantId", tenantId),
                        ("expiredOnly", request.Expired),
                        ("now", now),
                        ("expiringBefore", expiringBefore)
                    ],
                    token);
                if (total == 0)
                {
                    return BuildResult([], page, pageSize, total, summary);
                }

                // 3.3 (空行后) 查询列表
                await using var listCommand = CreateCommand(
                    connection,
                    BuildListSql(),
                    [
                        ("tenantId", tenantId),
                        ("expiredOnly", request.Expired),
                        ("now", now),
                        ("expiringBefore", expiringBefore),
                        ("offset", offset),
                        ("limit", pageSize)
                    ]);

                await using var reader = await listCommand.ExecuteReaderAsync(token);
                if (!reader.HasRows)
                {
                    return BuildResult([], page, pageSize, total, summary);
                }

                // 3.4 (空行后) 初始化字段序号
                var qualificationIdOrdinal = reader.GetOrdinal("QualificationId");
                var storeIdOrdinal = reader.GetOrdinal("StoreId");
                var storeNameOrdinal = reader.GetOrdinal("StoreName");
                var storeCodeOrdinal = reader.GetOrdinal("StoreCode");
                var tenantIdOrdinal = reader.GetOrdinal("TenantId");
                var tenantNameOrdinal = reader.GetOrdinal("TenantName");
                var typeOrdinal = reader.GetOrdinal("QualificationType");
                var expiresAtOrdinal = reader.GetOrdinal("ExpiresAt");
                var businessStatusOrdinal = reader.GetOrdinal("BusinessStatus");

                // 3.5 (空行后) 读取并映射
                List<StoreQualificationAlertDto> items = [];
                while (await reader.ReadAsync(token))
                {
                    DateOnly? expiresAt = reader.IsDBNull(expiresAtOrdinal)
                        ? null
                        : DateOnly.FromDateTime(reader.GetDateTime(expiresAtOrdinal));
                    var isExpired = expiresAt.HasValue && expiresAt.Value < now;
                    int? daysUntilExpiry = expiresAt.HasValue
                        ? expiresAt.Value.DayNumber - now.DayNumber
                        : null;

                    items.Add(new StoreQualificationAlertDto
                    {
                        QualificationId = reader.GetInt64(qualificationIdOrdinal),
                        StoreId = reader.GetInt64(storeIdOrdinal),
                        StoreName = reader.GetString(storeNameOrdinal),
                        StoreCode = reader.GetString(storeCodeOrdinal),
                        TenantId = reader.GetInt64(tenantIdOrdinal),
                        TenantName = reader.GetString(tenantNameOrdinal),
                        QualificationType = (StoreQualificationType)reader.GetInt32(typeOrdinal),
                        ExpiresAt = expiresAt,
                        DaysUntilExpiry = daysUntilExpiry,
                        IsExpired = isExpired,
                        StoreBusinessStatus = (StoreBusinessStatus)reader.GetInt32(businessStatusOrdinal)
                    });
                }

                // 3.6 (空行后) 组装结果
                return BuildResult(items, page, pageSize, total, summary);
            },
            cancellationToken);
    }

    private static StoreQualificationAlertResultDto BuildResult(
        IReadOnlyList<StoreQualificationAlertDto> items,
        int page,
        int pageSize,
        int totalCount,
        StoreQualificationAlertSummaryDto summary)
    {
        // 1. 计算总页数
        var totalPages = pageSize == 0 ? 0 : (int)Math.Ceiling(totalCount / (double)pageSize);

        // 2. (空行后) 组装分页结果
        return new StoreQualificationAlertResultDto
        {
            Items = items,
            Page = page,
            PageSize = pageSize,
            TotalCount = totalCount,
            TotalPages = totalPages,
            Summary = summary
        };
    }

    private static async Task<StoreQualificationAlertSummaryDto> ExecuteSummaryAsync(
        IDbConnection connection,
        DateOnly now,
        DateOnly expiringBefore,
        long tenantId,
        CancellationToken cancellationToken)
    {
        await using var command = CreateCommand(
            connection,
            BuildSummarySql(),
            [
                ("tenantId", tenantId),
                ("now", now),
                ("expiringBefore", expiringBefore)
            ]);

        await using var reader = await command.ExecuteReaderAsync(cancellationToken);
        if (!reader.HasRows || !await reader.ReadAsync(cancellationToken))
        {
            return new StoreQualificationAlertSummaryDto();
        }

        // 1. (空行后) 读取统计结果
        var expiringSoonOrdinal = reader.GetOrdinal("ExpiringSoonCount");
        var expiredOrdinal = reader.GetOrdinal("ExpiredCount");

        return new StoreQualificationAlertSummaryDto
        {
            ExpiringSoonCount = reader.IsDBNull(expiringSoonOrdinal) ? 0 : reader.GetInt32(expiringSoonOrdinal),
            ExpiredCount = reader.IsDBNull(expiredOrdinal) ? 0 : reader.GetInt32(expiredOrdinal)
        };
    }

    private static string BuildCountSql()
    {
        return """
            select count(*)
            from public.store_qualifications q
            join public.stores s on s."Id" = q."StoreId" and s."DeletedAt" is null
            join public.tenants t on t."Id" = s."TenantId" and t."DeletedAt" is null
            where q."DeletedAt" is null
              and q."ExpiresAt" is not null
              and s."TenantId" = @tenantId
              and (
                (@expiredOnly::boolean = true and q."ExpiresAt" < @now)
                or (@expiredOnly::boolean = false and q."ExpiresAt" <= @expiringBefore)
              );
            """;
    }

    private static string BuildListSql()
    {
        return """
            select
              q."Id" as "QualificationId",
              q."StoreId",
              s."Name" as "StoreName",
              s."Code" as "StoreCode",
              s."TenantId",
              t."Name" as "TenantName",
              q."QualificationType",
              q."ExpiresAt",
              s."BusinessStatus"
            from public.store_qualifications q
            join public.stores s on s."Id" = q."StoreId" and s."DeletedAt" is null
            join public.tenants t on t."Id" = s."TenantId" and t."DeletedAt" is null
            where q."DeletedAt" is null
              and q."ExpiresAt" is not null
              and s."TenantId" = @tenantId
              and (
                (@expiredOnly::boolean = true and q."ExpiresAt" < @now)
                or (@expiredOnly::boolean = false and q."ExpiresAt" <= @expiringBefore)
              )
            order by q."ExpiresAt" asc, q."Id" asc
            offset @offset
            limit @limit;
            """;
    }

    private static string BuildSummarySql()
    {
        return """
            select
              coalesce(sum(case when q."ExpiresAt" < @now then 1 else 0 end), 0) as "ExpiredCount",
              coalesce(sum(case when q."ExpiresAt" >= @now and q."ExpiresAt" <= @expiringBefore then 1 else 0 end), 0) as "ExpiringSoonCount"
            from public.store_qualifications q
            join public.stores s on s."Id" = q."StoreId" and s."DeletedAt" is null
            join public.tenants t on t."Id" = s."TenantId" and t."DeletedAt" is null
            where q."DeletedAt" is null
              and q."ExpiresAt" is not null
              and s."TenantId" = @tenantId;
            """;
    }

    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;

        // 1. (空行后) 绑定参数
        foreach (var (name, value) in parameters)
        {
            var parameter = command.CreateParameter();
            parameter.ParameterName = name;
            parameter.Value = value ?? DBNull.Value;
            command.Parameters.Add(parameter);
        }

        return (DbCommand)command;
    }
}
