using Baya.Application.Contracts.Common;
using Baya.Infrastructure.CrossCutting.Seams;
using Microsoft.Extensions.DependencyInjection;
namespace Baya.Infrastructure.CrossCutting.ServiceConfiguration;
public static class DevelopmentSeamExtensions
{
///
/// Development-only wiring for the OTP bring-up bridge. Registers and decorates
/// the registered by AddCrossCuttingSeams with
/// , so each OTP is also captured in memory for
/// /api/v1/dev/last_otp/{phone} to serve. MUST be called only inside
/// builder.Environment.IsDevelopment(), and only for a capture-safe provider (the log-only
/// or the Development-only Telegram relay — Program.cs owns that
/// condition): nothing here is wired in any other environment, which — together with the endpoint's own
/// IsDevelopment() guard — makes the OTP echo impossible to enable outside Development.
///
public static IServiceCollection AddDevelopmentOtpCapture(this IServiceCollection services)
{
services.AddSingleton();
// Decorate whatever ISmsSender is already registered rather than assuming the mock — with
// Seams:Sms:Provider = telegram the inner sender is TelegramSmsSender, and re-creating a LoggingSmsSender
// here would silently swallow the delivery instead of capturing alongside it. Last registration wins for
// a single resolve, so callers transparently get the decorator and delivery behaviour is unchanged.
var inner = services.LastOrDefault(d => d.ServiceType == typeof(ISmsSender))
?? throw new InvalidOperationException(
"AddDevelopmentOtpCapture must run after AddCrossCuttingSeams — no ISmsSender is registered.");
services.Remove(inner);
services.AddSingleton(sp => new DevCapturingSmsSender(
ResolveSender(sp, inner),
sp.GetRequiredService()));
return services;
}
private static ISmsSender ResolveSender(IServiceProvider sp, ServiceDescriptor descriptor) => descriptor switch
{
{ ImplementationInstance: ISmsSender instance } => instance,
{ ImplementationFactory: { } factory } => (ISmsSender)factory(sp),
{ ImplementationType: { } type } => (ISmsSender)ActivatorUtilities.CreateInstance(sp, type),
_ => throw new InvalidOperationException("The registered ISmsSender cannot be constructed for decoration."),
};
///
/// Development/Testing-only re-registration of the real over the
/// production (refinement-phase-8, 6.4). The bookings/convert
/// simulator path is a dev/test affordance — production converts via the real b10 webhook confirm, not this
/// command. Last registration wins, so callers transparently get the succeeding mock in Dev/Testing.
///
public static IServiceCollection AddDevelopmentPaymentCapture(this IServiceCollection services)
{
services.AddSingleton();
return services;
}
}