using Kbx.Shared.Integrations.Generated;

namespace Kbx.Shared.Integrations;

public sealed class KbxOutboxIntegrationDispatcher(
    IKbxIntegrationAdapterRegistry adapters,
    KbxIntegrationAttemptRepository attempts,
    KbxPollyIntegrationPipeline pipelineFactory,
    KbxIntegrationOperationsProjector operations)
{
    public async Task<KbxIntegrationAttemptResult> DispatchAsync(KbxIntegrationMessage message, int durableAttempt, CancellationToken ct)
    {
        if (!KbxIntegrationCatalog.All.TryGetValue(message.IntegrationId, out var definition))
            return new(false, KbxIntegrationFailureKind.Permanent, "INTEGRATION_NOT_REGISTERED", message.IntegrationId);

        var attemptId = await attempts.StartAsync(message, durableAttempt, ct);
        var pipeline = pipelineFactory.Build(
            definition.ShortRetryAttempts,
            TimeSpan.FromMilliseconds(definition.ShortRetryBaseDelayMs),
            TimeSpan.FromMilliseconds(definition.TimeoutMs),
            definition.CircuitFailureRatio,
            TimeSpan.FromSeconds(definition.CircuitSamplingSeconds),
            definition.CircuitMinimumThroughput,
            TimeSpan.FromSeconds(definition.CircuitBreakSeconds));

        KbxIntegrationAttemptResult result;
        try
        {
            var adapter = adapters.GetRequired(message.IntegrationId);
            result = await pipeline.ExecuteAsync(async token => await adapter.SendAsync(message, token), ct);
        }
        catch (Exception error)
        {
            result = new(false, KbxIntegrationFailureClassifier.Classify(error), error.GetType().Name, "외부 연계 호출이 완료되지 않았습니다.");
        }

        // A transient result is persisted and delegated to Hangfire for durable retry.
        var state = result.Succeeded ? "delivered" : result.FailureKind == KbxIntegrationFailureKind.Transient ? "retrying" : "failed";
        var next = state == "retrying" ? KbxDurableIntegrationRetryPlanner.NextAttemptAt(definition, durableAttempt, DateTimeOffset.UtcNow) : null;
        await attempts.CompleteAsync(attemptId, result, state, next, ct);
        if (result.Succeeded) await operations.OnDeliveredAsync(message, definition, ct);
        else if (result.FailureKind == KbxIntegrationFailureKind.Permanent && definition.TerminalAction == "operations-exception")
            await operations.OnPermanentFailureAsync(message, definition, result, ct);
        return result;
    }
}
