using Dapper;
using FastEndpoints;
using Npgsql;
using Shared.Operations;
using System.Text.Json;

namespace Modules.Common.Operations.Search;

public sealed class SearchWorkItemsRequest
{
    public string? Module { get; init; }
    public string? Severity { get; init; }
    public string? Status { get; init; }
    public string? Owner { get; init; }
    public string? Code { get; init; }
    public string? Keyword { get; init; }
    public int Page { get; init; } = 1;
    public int PageSize { get; init; } = 200;
}

public sealed class Endpoint(NpgsqlDataSource dataSource) : Endpoint<SearchWorkItemsRequest, WorkQueueResponse>
{
    public override void Configure()
    {
        Get("/api/operations/work-items");
        Permissions("common.operations.read");
    }

    public override async Task HandleAsync(SearchWorkItemsRequest req, CancellationToken ct)
    {
        var tenantId = OperationsIdentity.TenantId(User);
        var userId = OperationsIdentity.UserId(User);
        var page = Math.Max(1, req.Page);
        var pageSize = Math.Clamp(req.PageSize, 1, 500);
        var offset = (page - 1) * pageSize;
        await using var connection = await dataSource.OpenConnectionAsync(ct);

        const string rowsSql = """
        select id, source_module as SourceModule, source_type as SourceType, source_id as SourceId,
               reference_no as ReferenceNo, source_screen_id as SourceScreenId, code, title, detail, severity, status, owner_id as OwnerId, owner_name as OwnerName,
               occurred_at as OccurredAt, due_at as DueAt,
               greatest(0, floor(extract(epoch from (now() - occurred_at)) / 60))::int as AgeMinutes,
               version, context::text as ContextJson, retry_action_key as RetryActionKey,
               allow_manual_resolution as AllowManualResolution
          from kbx.work_items
         where tenant_id = @TenantId
           and (@Module is null or source_module = @Module)
           and (@Severity is null or severity = @Severity)
           and (@Status is null or status = @Status)
           and (@Code is null or code = @Code)
           and (@Keyword is null or title ilike '%' || @Keyword || '%' or detail ilike '%' || @Keyword || '%' or reference_no ilike '%' || @Keyword || '%' or source_id ilike '%' || @Keyword || '%')
           and (@Owner is null
                or (@Owner = 'mine' and owner_id = @UserId)
                or (@Owner = 'unassigned' and owner_id is null))
         order by
            case severity when 'critical' then 0 when 'warning' then 1 else 2 end,
            coalesce(due_at, occurred_at), occurred_at
         limit @PageSize offset @Offset;
        """;

        const string countSql = """
        select count(*)::int
          from kbx.work_items
         where tenant_id = @TenantId
           and (@Module is null or source_module = @Module)
           and (@Severity is null or severity = @Severity)
           and (@Status is null or status = @Status)
           and (@Code is null or code = @Code)
           and (@Keyword is null or title ilike '%' || @Keyword || '%' or detail ilike '%' || @Keyword || '%' or reference_no ilike '%' || @Keyword || '%' or source_id ilike '%' || @Keyword || '%')
           and (@Owner is null
                or (@Owner = 'mine' and owner_id = @UserId)
                or (@Owner = 'unassigned' and owner_id is null));
        """;

        const string countersSql = """
        select code as Key, min(title) as Label, count(*)::int as Count,
               case max(case severity when 'critical' then 3 when 'warning' then 2 else 1 end)
                 when 3 then 'critical' when 2 then 'warning' else 'info' end as Severity
          from kbx.work_items
         where tenant_id = @TenantId and status in ('open','claimed')
         group by code
         order by max(case severity when 'critical' then 3 when 'warning' then 2 else 1 end) desc, count(*) desc
         limit 12;
        """;

        var args = new {
            TenantId = tenantId,
            UserId = userId,
            Module = EmptyToNull(req.Module),
            Severity = EmptyToNull(req.Severity),
            Status = EmptyToNull(req.Status),
            Owner = EmptyToNull(req.Owner),
            Code = EmptyToNull(req.Code),
            Keyword = EmptyToNull(req.Keyword),
            PageSize = pageSize,
            Offset = offset,
        };

        var rows = (await connection.QueryAsync<WorkItemProjection>(new CommandDefinition(rowsSql, args, cancellationToken: ct))).AsList();
        var total = await connection.ExecuteScalarAsync<int>(new CommandDefinition(countSql, args, cancellationToken: ct));
        var counters = (await connection.QueryAsync<WorkQueueCounter>(new CommandDefinition(countersSql, new { TenantId = tenantId }, cancellationToken: ct))).AsList();
        var items = rows.Select(ToDto).ToList();
        await Send.OkAsync(new WorkQueueResponse(items, total, counters), ct);
    }

    private static string? EmptyToNull(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim();

    private static WorkItemDto ToDto(WorkItemProjection row)
    {
        var actions = new List<WorkItemActionDto> {
            new("navigate", "원 업무 보기", "navigate"),
        };
        if (WorkItemActionPolicy.CanClaim(row.Status)) actions.Add(new("claim", "내가 처리", "claim", "common.operations.claim"));
        if (WorkItemActionPolicy.CanRetry(row.Status, row.RetryActionKey)) actions.Add(new("retry", "재처리", "retry", "common.operations.retry"));
        if (WorkItemActionPolicy.CanManualResolve(row.Status, row.AllowManualResolution)) actions.Add(new("resolve", "해결 처리", "resolve", "common.operations.resolve"));
        var context = JsonSerializer.Deserialize<JsonElement>(row.ContextJson);
        return new(row.Id, row.SourceModule, row.SourceType, row.SourceId, row.ReferenceNo, row.SourceScreenId, row.Code, row.Title, row.Detail,
            row.Severity, row.Status, row.OwnerId, row.OwnerName, row.OccurredAt, row.DueAt, row.AgeMinutes,
            row.Version, context, actions);
    }
}
