using Dapper;
using Npgsql;

namespace Modules.OMS.Orders.Search;

public sealed class SearchOrdersHandler(NpgsqlDataSource dataSource)
{
    public async Task<SearchOrdersResponse> HandleAsync(SearchOrdersRequest request, CancellationToken ct)
    {
        var page = Math.Max(request.Page, 1);
        var pageSize = Math.Clamp(request.PageSize, 1, 500);
        var offset = (page - 1) * pageSize;

        await using var connection = await dataSource.OpenConnectionAsync(ct);

        // Read model query: UI-required projection in one server-side query boundary.
        const string rowsSql = """
        select
            o.id,
            o.order_no as OrderNo,
            ch.name as ChannelName,
            o.ordered_at as OrderedAt,
            o.customer_name as CustomerName,
            o.item_summary as ItemSummary,
            o.total_qty as TotalQty,
            o.amount as Amount,
            o.allocation_status as AllocationStatus,
            o.shipment_status as ShipmentStatus,
            o.exception_count as ExceptionCount
        from oms_order_search_projection o
        join sales_channel ch on ch.id = o.channel_id
        where o.ordered_at >= @From
          and o.ordered_at < @ToExclusive
          and (@ChannelId is null or o.channel_id = @ChannelId)
          and (@Status is null or o.shipment_status = @Status)
          and (@Keyword is null or o.search_text ilike '%' || @Keyword || '%')
          and (@ExceptionOnly = false or o.exception_count > 0)
        order by o.ordered_at desc, o.id desc
        limit @PageSize offset @Offset;
        """;

        const string summarySql = """
        select
            count(*)::int as TotalCount,
            coalesce(sum(o.total_qty), 0) as TotalQty,
            coalesce(sum(o.amount), 0) as TotalAmount,
            count(*) filter (where o.order_status = 'NEW')::int as NewCount,
            count(*) filter (where o.shipment_status = 'READY')::int as ReadyToShipCount,
            count(*) filter (where o.exception_count > 0)::int as ExceptionCount
        from oms_order_search_projection o
        where o.ordered_at >= @From
          and o.ordered_at < @ToExclusive
          and (@ChannelId is null or o.channel_id = @ChannelId)
          and (@Status is null or o.shipment_status = @Status)
          and (@Keyword is null or o.search_text ilike '%' || @Keyword || '%')
          and (@ExceptionOnly = false or o.exception_count > 0);
        """;

        var args = new
        {
            From = request.From.ToDateTime(TimeOnly.MinValue),
            ToExclusive = request.To.AddDays(1).ToDateTime(TimeOnly.MinValue),
            request.ChannelId,
            Status = string.IsNullOrWhiteSpace(request.Status) ? null : request.Status,
            Keyword = string.IsNullOrWhiteSpace(request.Keyword) ? null : request.Keyword.Trim(),
            request.ExceptionOnly,
            PageSize = pageSize,
            Offset = offset,
        };

        var rows = (await connection.QueryAsync<OrderSearchRow>(
            new CommandDefinition(rowsSql, args, cancellationToken: ct))).AsList();

        var summary = await connection.QuerySingleAsync<SummaryRow>(
            new CommandDefinition(summarySql, args, cancellationToken: ct));

        return new SearchOrdersResponse(
            rows,
            summary.TotalCount,
            summary.TotalQty,
            summary.TotalAmount,
            new OrderSearchCounters(
                summary.TotalCount,
                summary.NewCount,
                summary.ReadyToShipCount,
                summary.ExceptionCount));
    }

    private sealed record SummaryRow(
        int TotalCount,
        decimal TotalQty,
        decimal TotalAmount,
        int NewCount,
        int ReadyToShipCount,
        int ExceptionCount);
}
