using Deal.Grpc.Hosting.Interceptors;
using Deal.Grpc.Hosting.Models;
using Deal.Grpc.Hosting.Options;
using Deal.Grpc.Hosting.Services;
namespace Deal.Ai;
///
/// Собирает WebApplication gRPC-хоста ai-service.
///
public static class AiServiceHost
{
///
/// Создаёт (не запускает) хост
///
/// TCP-порт Kestrel.
/// Аргументы командной строки (Program.cs); в тестах не нужны.
/// Опциональный хук DI для тестов (подмена LLM-фасада фейками: регистрация, добавленная здесь, побеждает — DI резолвит последнюю; см. AiServiceHostTests/AiRpcTests).
/// Опциональный хук конфигурации билдера для production-точки входа (Program.cs): Serilog. Тесты хост поднимают БЕЗ этого хука — логирование файлов/консоли тестам не нужно.
/// Собранный хост; запуск — StartAsync/RunAsync у вызывающего.
public static WebApplication Create(
int grpcPort,
string[]? args = null,
Action? configureServices = null,
Action? configureBuilder = null)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args ?? []);
// Общая серверная обвязка (Deal.Grpc.Hosting, C31): mTLS env DEAL_MTLS_* — загрузка
// сертификатов сразу с fail-fast (compose-prod монтирует deploy/certs, scripts/mtls-certs.sh);
MtlsCertificates? mtlsCertificates = GrpcServer.LoadMtlsCertificates(builder);
GrpcServer.ConfigureKestrelHttp2Endpoint(builder, grpcPort, mtlsCertificates);
builder.Services.AddDealGrpcServer();
builder.Services.AddReadyHealthCheck("хост ai-service готов");
// попытки управляется внутри — 90 с OpenAI / 60 с Anthropic; клиент без общего таймаута) и
// оркестратор ретраев/JSON/usage поверх него. Ключи API — в конфиге запроса, не в DI/логах.
builder.Services.AddHttpClient(static httpClient =>
httpClient.Timeout = Timeout.InfiniteTimeSpan);
builder.Services.AddSingleton(
static sp => sp.GetRequiredService());
builder.Services.AddSingleton>(DefaultRetryDelayAsync);
builder.Services.AddSingleton();
configureServices?.Invoke(builder.Services);
configureBuilder?.Invoke(builder);
WebApplication app = builder.Build();
app.MapGrpcService();
app.MapGrpcHealthChecksService();
return app;
}
// Пауза между ретраями по умолчанию (Task.Delay; тесты подменяют через DI).
// delay: Длительность паузы.
// cancellationToken: Токен отмены.
private static Task DefaultRetryDelayAsync(TimeSpan delay, CancellationToken cancellationToken)
=> Task.Delay(delay, cancellationToken);
}