using Dapper;
using Npgsql;
using System.Text.Json;

namespace Shared.Operations;

public sealed record UpsertWorkItem(
    string TenantId,
    string SourceModule,
    string SourceType,
    string SourceId,
    string ReferenceNo,
    string? SourceScreenId,
    long? SourceVersion,
    string Code,
    string Title,
    string? Detail,
    string Severity,
    DateTimeOffset OccurredAt,
    DateTimeOffset? DueAt = null,
    string? RetryActionKey = null,
    bool AllowManualResolution = false,
    object? Context = null);

public sealed class OperationsProjectionWriter(NpgsqlDataSource dataSource)
{
    public async Task<Guid> UpsertAsync(UpsertWorkItem item, CancellationToken ct)
    {
        await using var connection = await dataSource.OpenConnectionAsync(ct);
        var id = Guid.NewGuid();
        const string sql = """
        insert into kbx.work_items(
            id, tenant_id, source_module, source_type, source_id, reference_no, source_screen_id, source_version,
            code, title, detail, severity, status, retry_action_key, allow_manual_resolution,
            context, occurred_at, due_at)
        values(
            @Id, @TenantId, @SourceModule, @SourceType, @SourceId, @ReferenceNo, @SourceScreenId, @SourceVersion,
            @Code, @Title, @Detail, @Severity, 'open', @RetryActionKey, @AllowManualResolution,
            cast(@Context as jsonb), @OccurredAt, @DueAt)
        on conflict(tenant_id, source_module, source_type, source_id, code)
        do update set
            source_version = excluded.source_version,
            reference_no = excluded.reference_no,
            source_screen_id = excluded.source_screen_id,
            title = excluded.title,
            detail = excluded.detail,
            severity = excluded.severity,
            status = case when kbx.work_items.status = 'resolved' then 'open' else kbx.work_items.status end,
            retry_action_key = excluded.retry_action_key,
            allow_manual_resolution = excluded.allow_manual_resolution,
            context = excluded.context,
            occurred_at = excluded.occurred_at,
            due_at = excluded.due_at,
            resolved_at = null,
            resolution_reason = null,
            version = kbx.work_items.version + 1,
            updated_at = now()
        where excluded.source_version is null
           or kbx.work_items.source_version is null
           or excluded.source_version > kbx.work_items.source_version
        returning id;
        """;
        var args = new {
            Id = id,
            item.TenantId, item.SourceModule, item.SourceType, item.SourceId, item.ReferenceNo, item.SourceScreenId, item.SourceVersion,
            item.Code, item.Title, item.Detail, item.Severity, item.RetryActionKey, item.AllowManualResolution,
            Context = JsonSerializer.Serialize(item.Context ?? new { }), item.OccurredAt, item.DueAt
        };
        var changedId = await connection.ExecuteScalarAsync<Guid?>(new CommandDefinition(sql, args, cancellationToken: ct));
        if (changedId is not null) return changedId.Value;
        return await connection.ExecuteScalarAsync<Guid>(new CommandDefinition("""
            select id from kbx.work_items
             where tenant_id=@TenantId and source_module=@SourceModule and source_type=@SourceType
               and source_id=@SourceId and code=@Code;
        """, args, cancellationToken: ct));
    }

    public async Task ResolveBySourceAsync(string tenantId, string sourceModule, string sourceType, string sourceId, string code, string reason, long? sourceVersion, CancellationToken ct)
    {
        await using var connection = await dataSource.OpenConnectionAsync(ct);
        await connection.ExecuteAsync(new CommandDefinition("""
            update kbx.work_items
               set status = 'resolved', resolved_at = now(), resolution_reason = @Reason,
                   source_version = coalesce(@SourceVersion, source_version),
                   version = version + 1, updated_at = now()
             where tenant_id = @TenantId and source_module = @SourceModule and source_type = @SourceType
               and source_id = @SourceId and code = @Code and status <> 'resolved'
               and (@SourceVersion is null or source_version is null or @SourceVersion >= source_version);
        """, new { TenantId = tenantId, SourceModule = sourceModule, SourceType = sourceType, SourceId = sourceId, Code = code, Reason = reason, SourceVersion = sourceVersion }, cancellationToken: ct));
    }
}
