using Elastic.Clients.Elasticsearch;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Prometheus;
using Serilog;
using StackExchange.Redis;
using System.Text.Json.Serialization;
using FluentValidation;
using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Mvc;
using System.Reflection;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Authorization;
using System.Text;
using Microsoft.OpenApi;
using Microsoft.AspNetCore.DataProtection;

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateBootstrapLogger();

try
{    
    var builder = WebApplication.CreateBuilder(args);

    builder.Services.Configure<JwtOptions>(builder.Configuration.GetSection(JwtOptions.Section));

    var jwtOptions = builder.Configuration.GetSection(JwtOptions.Section).Get<JwtOptions>()!;

    if (string.IsNullOrWhiteSpace(jwtOptions.SigningKey)
        || Encoding.UTF8.GetByteCount(jwtOptions.SigningKey) < 32)
        throw new InvalidOperationException(
            "Jwt:SigningKey must be configured and at least 256 bits (32 bytes) for HMAC-SHA256.");

    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = jwtOptions.Issuer,
                ValidAudience = jwtOptions.Audience,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SigningKey))
            };
        })
        .AddScheme<AuthenticationSchemeOptions, GatewayApiKeyAuthenticationHandler>(
            GatewayApiKeyAuthenticationHandler.SchemeName, null);

    builder.Services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
    builder.Services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
    builder.Services.AddAuthorization(options =>
    {
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
    });


    builder.Services.AddFluentValidationAutoValidation();
    builder.Services.AddValidatorsFromAssemblyContaining<Program>();

    // Serilog's reloadable logger can only be frozen once per process; skip in
    // integration tests where WebApplicationFactory may build multiple hosts.
    if (!builder.Environment.IsEnvironment("Testing"))
    {
        builder.Host.UseSerilog((ctx, services, config) =>
            config.ReadFrom.Configuration(ctx.Configuration)
                  .ReadFrom.Services(services)
                  .Enrich.FromLogContext()
                  .Enrich.WithMachineName()
                  .Enrich.WithThreadId());
    }

    builder.Services.AddDbContext<AppDbContext>((sp, opts) =>
    {
        opts.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"));
    });

    builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
        ConnectionMultiplexer.Connect(sp.GetRequiredService<IConfiguration>()["Redis:ConnectionString"]!));

    builder.Services.Configure<KafkaOptions>(
    builder.Configuration.GetSection(KafkaOptions.Section));

    builder.Services.Configure<ElasticsearchOptions>(
    builder.Configuration.GetSection(ElasticsearchOptions.Section));

    var esOptions = builder.Configuration
        .GetSection(ElasticsearchOptions.Section)
        .Get<ElasticsearchOptions>()!;

    builder.Services.AddSingleton(_ =>
    {
        var settings = new ElasticsearchClientSettings(new Uri(esOptions.Uri));

        if (!string.IsNullOrWhiteSpace(esOptions.ApiKey))
            settings = settings.Authentication(new Elastic.Transport.ApiKey(esOptions.ApiKey));
        else if (!string.IsNullOrWhiteSpace(esOptions.Username))
            settings = settings.Authentication(
                new Elastic.Transport.BasicAuthentication(esOptions.Username, esOptions.Password ?? ""));

        if (esOptions.DisableCertificateValidation)
            settings = settings.ServerCertificateValidationCallback((_, _, _, _) => true);

        return new ElasticsearchClient(settings);
    });

    builder.Services.Configure<RabbitMqOptions>(
    builder.Configuration.GetSection(RabbitMqOptions.Section));
    builder.Services.Configure<MinioOptions>(
        builder.Configuration.GetSection(MinioOptions.Section));

    builder.Services.AddSingleton<RabbitMqTopologyProvisioner>();
    builder.Services.AddHostedService(sp => sp.GetRequiredService<RabbitMqTopologyProvisioner>());

    builder.Services.Configure<ReconciliationJobOptions>(
    builder.Configuration.GetSection(ReconciliationJobOptions.Section));

    builder.Services.Configure<DataLakeOptions>(
    builder.Configuration.GetSection(DataLakeOptions.Section));

    builder.Services.Configure<TrendDetectionOptions>(
    builder.Configuration.GetSection(TrendDetectionOptions.SectionName));

    builder.Services.Configure<SuppressionOptions>(
    builder.Configuration.GetSection(SuppressionOptions.SectionName));

    builder.Services.Configure<MedicationCorrelationOptions>(
    builder.Configuration.GetSection(MedicationCorrelationOptions.SectionName));

    builder.Services.Configure<DashboardOptions>(
        builder.Configuration.GetSection(DashboardOptions.Section));

    builder.Services.Configure<SofaOptions>(builder.Configuration.GetSection("Sofa"));

    var dashboardOptions = builder.Configuration
        .GetSection(DashboardOptions.Section)
        .Get<DashboardOptions>() ?? new DashboardOptions();

    builder.Services.Configure<ClinicalSyncOptions>(
        builder.Configuration.GetSection(ClinicalSyncOptions.Section));

    builder.Services.Configure<FhirOptions>(
        builder.Configuration.GetSection(FhirOptions.Section));

    builder.Services.Configure<PatientOptions>(
        builder.Configuration.GetSection(PatientOptions.Section));

    builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(
        builder.Configuration["DataProtection:KeyPath"] ?? "./data-protection-keys"))
    .SetApplicationName("VigilCareClinical");

    builder.Services.Configure<PhiEncryptionOptions>(
    builder.Configuration.GetSection(PhiEncryptionOptions.Section));

    builder.Services.Configure<ClinicalSyncOptions>(builder.Configuration.GetSection(ClinicalSyncOptions.Section));
    
    builder.Services.Configure<GatewayMonitoringOptions>(
    builder.Configuration.GetSection(GatewayMonitoringOptions.Section));

    builder.Services.Configure<AlertQualityOptions>(
        builder.Configuration.GetSection(AlertQualityOptions.Section));

    builder.Services.Configure<SimulationOptions>(
        builder.Configuration.GetSection(SimulationOptions.Section));

    var simulationOptions = builder.Configuration
        .GetSection(SimulationOptions.Section).Get<SimulationOptions>() ?? new();

    if (simulationOptions.Enabled)
    {
        builder.Services.AddHttpClient("simulation-loopback", c =>
            c.BaseAddress = new Uri(simulationOptions.LoopbackBaseUrl));
        builder.Services.AddSingleton<ISimulationClientFactory, SimulationClientFactory>();
        builder.Services.AddSingleton<IScenarioCatalog, ScenarioCatalog>();
        builder.Services.AddSingleton<SimulationRunner>();
        builder.Services.AddSingleton<ISimulationRunner>(sp => sp.GetRequiredService<SimulationRunner>());
        builder.Services.AddHostedService(sp => sp.GetRequiredService<SimulationRunner>());
    }

    builder.Services.AddCors(options =>
    {
        options.AddPolicy("Dashboard", policy =>
        {
            policy.WithOrigins(dashboardOptions.CorsOrigins)
                .AllowAnyHeader()
                .AllowAnyMethod();
        });
    });

    builder.Services.AddScoped<IPatientService, PatientService>();
    builder.Services.AddScoped<IEncounterService, EncounterService>();
    builder.Services.AddScoped<IDischargeSummaryService, DischargeSummaryService>();
    builder.Services.AddScoped<IAlertThresholdService, AlertThresholdService>();
    builder.Services.AddScoped<IObservationService, ObservationService>();
    builder.Services.AddScoped<IObservationQueryService, ObservationQueryService>();
    builder.Services.AddScoped<IAlertService, AlertService>();
    builder.Services.AddScoped<IOrderService, OrderService>();
    builder.Services.AddScoped<IAnalyticsService, AnalyticsService>();
    builder.Services.AddScoped<INews2Service, News2Service>();
    builder.Services.AddScoped<IQsofaService, QsofaService>();
    builder.Services.AddScoped<ISepsisBundleService, SepsisBundleService>();
    builder.Services.AddScoped<SepsisAlertHandler>();
    builder.Services.AddScoped<QsofaDetector>();
    builder.Services.AddScoped<UnacknowledgedAlertsCheck>();
    builder.Services.AddScoped<PendingOrdersCheck>();
    builder.Services.AddScoped<DisconnectedMonitorsCheck>();
    builder.Services.AddScoped<ReconciliationPublisher>();    
    builder.Services.AddScoped<WarningEvaluator>();
    builder.Services.AddScoped<News2Detector>();
    builder.Services.AddScoped<TrendDetector>();
    builder.Services.AddSingleton<IAlertSuppressionService, AlertSuppressionService>();
    builder.Services.AddScoped<IMedicationService, MedicationService>();
    builder.Services.AddScoped<MedicationCorrelationHelper>();
    builder.Services.AddScoped<GcsDetector>();
    builder.Services.AddScoped<IGcsService, GcsService>();
    builder.Services.AddScoped<SofaLabCache>();
    builder.Services.AddScoped<SofaVasopressorResolver>();
    builder.Services.AddScoped<SofaDetector>();
    builder.Services.AddScoped<ISofaService, SofaService>();
    builder.Services.AddScoped<IExternalIdentifierService, ExternalIdentifierService>();
    builder.Services.AddScoped<FhirReferenceResolver>();
    builder.Services.AddScoped<PatientFhirMapper>();
    builder.Services.AddScoped<EncounterFhirMapper>();
    builder.Services.AddScoped<ObservationFhirMapper>();
    builder.Services.AddScoped<MedicationAdministrationFhirMapper>();
    builder.Services.AddScoped<FhirBundleProcessor>();
    builder.Services.AddScoped<FhirExceptionFilter>();
    builder.Services.AddHttpContextAccessor();
    builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
    builder.Services.AddScoped<IAuthService, AuthService>();
    builder.Services.AddScoped<IUserService, UserService>();
    builder.Services.AddScoped<IAuditService, AuditService>();
    builder.Services.AddScoped<IClinicalSyncService, ClinicalSyncService>();
    builder.Services.AddScoped<ClinicalSyncBatchProcessor>();
    builder.Services.AddSingleton<IPhiEncryptionService, PhiEncryptionService>();
    builder.Services.AddScoped<IPhiAccessLogService, PhiAccessLogService>();
    builder.Services.AddScoped<ISiteService, SiteService>();
    builder.Services.AddScoped<IGatewayRegistryService, GatewayRegistryService>();
    builder.Services.AddScoped<IOperationsService, OperationsService>();
    builder.Services.AddHostedService<GatewayStaleDetectorService>();
    builder.Services.AddScoped<IAlertQualityMetricsService, AlertQualityMetricsService>();

    builder.Services.AddHostedService<ThresholdCacheLoader>();
    builder.Services.AddHostedService<KafkaTopicProvisioner>();
    builder.Services.AddHostedService<OutboxRelayService>();
    builder.Services.AddHostedService<ClinicalSyncBatchConsumer>();
    builder.Services.AddHostedService<ElasticIndexProvisioner>();
    builder.Services.AddHostedService<EsIndexerService>();
    builder.Services.AddHostedService<SepsisEngineService>();
    builder.Services.AddHostedService<NotificationPublisherService>();
    builder.Services.AddHostedService<PagingWorkerService>();
    builder.Services.AddHostedService<EscalationWorkerService>();
    builder.Services.AddHostedService<DischargeSummaryWorkerService>();
    builder.Services.AddHostedService<ReconciliationScheduler>();
    builder.Services.AddSingleton<ClinicalMetrics>();
    builder.Services.AddHostedService<AlertsUnacknowledgedCollector>();
    builder.Services.AddHostedService<OutboxPendingCollector>();
    builder.Services.AddHostedService<KafkaConsumerLagCollector>();
    builder.Services.AddHostedService<DataLakeWriterService>();
    builder.Services.AddHostedService<WarningAlertService>();
    builder.Services.AddHostedService<News2ScoringService>();
    builder.Services.AddHostedService<TrendAnalyzerService>();
    builder.Services.AddHostedService<SepsisBundleMonitorService>();
    builder.Services.AddHostedService<GcsScoringService>();
    builder.Services.AddHostedService<SofaScoringService>();
    builder.Services.AddHostedService<PatientPhiMigrationService>();
    builder.Services.AddHostedService<WardGatewayMetricsCollector>();
    builder.Services.AddHostedService<AlertQualityAggregatorService>();

    builder.Services.AddHealthChecks()
        .AddDbContextCheck<AppDbContext>("postgresql", tags: new[] { "ready" })
        .AddCheck<RedisHealthCheck>("redis", tags: new[] { "ready" })
        .AddCheck<KafkaHealthCheck>("kafka", tags: new[] { "ready" })
        .AddCheck<RabbitMqHealthCheck>("rabbitmq", tags: new[] { "ready" })
        .AddCheck<ElasticsearchHealthCheck>("elasticsearch", tags: new[] { "ready" });

    builder.Services.AddControllers()
        .AddJsonOptions(opts =>
        {
            opts.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
            opts.JsonSerializerOptions.Converters.Add(new BloodTypeJsonConverter());
            opts.JsonSerializerOptions.Converters.Add(new AuditActionJsonConverter());
            opts.JsonSerializerOptions.Converters.Add(new SepsisBundleComplianceStatusJsonConverter());
            opts.JsonSerializerOptions.Converters.Add(new AlertTypeJsonConverter());
            opts.JsonSerializerOptions.Converters.Add(new AlertSeverityJsonConverter());
            opts.JsonSerializerOptions.Converters.Add(new AlertStatusJsonConverter());
            opts.JsonSerializerOptions.Converters.Add(new ObservationSourceJsonConverter());
            opts.JsonSerializerOptions.Converters.Add(new DepartmentJsonConverter());
            opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        });
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen(options =>
    {
        options.SwaggerDoc("v1", new OpenApiInfo
        {
            Title = "VigilCare Clinical API",
            Version = "v1",
            Description = "Clinical monitoring and alerting platform API"
        });

        options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
        {
            Name = "Authorization",
            Type = SecuritySchemeType.Http,
            Scheme = "bearer",
            BearerFormat = "JWT",
            In = ParameterLocation.Header,
            Description = "Enter your JWT token"
        });

        options.AddSecurityRequirement(document => new OpenApiSecurityRequirement
        {
            {
                new OpenApiSecuritySchemeReference("Bearer", document),
                new List<string>()
            }
        });

        var xmlPath = Path.Combine(AppContext.BaseDirectory,
            $"{Assembly.GetExecutingAssembly().GetName().Name}.xml");
        options.IncludeXmlComments(xmlPath);
    });

    builder.Services.Configure<ApiBehaviorOptions>(options =>
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            var errors = context.ModelState
                .Where(e => e.Value?.Errors.Count > 0)
                .SelectMany(e => e.Value!.Errors.Select(err => new
                {
                    field = e.Key,
                    message = err.ErrorMessage
                }))
                .ToList();

            var response = ApiResponse<object>.Fail(400,
                "One or more validation errors occurred.", "VALIDATION_ERROR");

            return new BadRequestObjectResult(new
            {
                response.Success,
                response.StatusCode,
                data = (object?)null,
                error = new { message = "One or more validation errors occurred.", code = "VALIDATION_ERROR", details = errors }
            });
        };
    });

    var app = builder.Build();

    // One-shot CLI verbs exit before hosting. Skip demo seeding for them so
    // create-admin / register-gateway never race the demo UserSeeder.
    var isCliCommand = args.Contains("encrypt-phi")
                       || args.Contains("create-admin")
                       || args.Contains("register-gateway");

    if (simulationOptions.Enabled && !isCliCommand)
    {
        Log.Warning(
            "Simulation mode ENABLED — scenario replay endpoints are exposed. " +
            "Do not run this configuration against real patient data.");
    }

    // Demo data — including the seeded demo users with well-known passwords —
    // must never be created in production. Seeding:EnableDemoData defaults to
    // true so local development and the existing verification scripts are
    // unaffected; appsettings.Production.json sets it to false.
    var enableDemoData = builder.Configuration.GetValue("Seeding:EnableDemoData", true)
                         && !app.Environment.IsEnvironment("Testing")
                         && !isCliCommand;

    if (enableDemoData)
    {
        using var scope = app.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();
        await DataSeeder.SeedAsync(db, redis);
        await GatewayRegistrySeeder.SeedAsync(db);
        await UserSeeder.SeedAsync(
            db,
            simulationEnabled: simulationOptions.Enabled,
            simulationRunnerPassword: simulationOptions.RunnerPassword);
    }
    else if (simulationOptions.Enabled && !isCliCommand && !app.Environment.IsEnvironment("Testing"))
    {
        using var scope = app.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await UserSeeder.EnsureSimulationRunnerAsync(db, simulationOptions.RunnerPassword);
    }

    if (!app.Environment.IsEnvironment("Testing"))
    {
        app.UseSerilogRequestLogging(options =>
        {
            options.MessageTemplate =
                "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.000}ms";
        });
    }

    if (builder.Configuration.GetValue("Swagger:Enabled", !app.Environment.IsProduction()))
    {
        app.UseSwagger();
        app.UseSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "VigilCare Clinical API v1");
        });
    }

    app.UseMiddleware<CorrelationIdMiddleware>();
    app.UseMiddleware<FhirApiKeyOrJwtMiddleware>();
    app.UseMiddleware<ExceptionHandlerMiddleware>();

    app.UseCors("Dashboard");

    app.UseAuthentication();
    app.UseAuthorization();

    app.MapHealthChecks("/health/live", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
    {
        Predicate = _ => false,
        ResponseWriter = HealthCheckResponseWriter.WriteAsync
    }).AllowAnonymous();

    app.MapHealthChecks("/health/ready", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions
    {
        Predicate = check => check.Tags.Contains("ready"),
        ResponseWriter = HealthCheckResponseWriter.WriteAsync
    }).AllowAnonymous();

    app.MapMetrics("/metrics").AllowAnonymous();
    app.MapControllers();

    if (args.Contains("encrypt-phi"))
    {
        await EncryptPhiCommand.RunAsync(app.Services);
        return;
    }

    if (args.Contains("create-admin"))
    {
        try
        {
            Environment.ExitCode = await CreateAdminCommand.RunAsync(app.Services, args);
        }
        catch (ArgumentException ex)
        {
            Console.Error.WriteLine(ex.Message);
            Environment.ExitCode = 1;
        }
        return;
    }

    if (args.Contains("register-gateway"))
    {
        try
        {
            Environment.ExitCode = await RegisterGatewayCommand.RunAsync(app.Services, args);
        }
        catch (ArgumentException ex)
        {
            Console.Error.WriteLine(ex.Message);
            Environment.ExitCode = 1;
        }
        return;
    }

    app.Run();

}
/***
dotnet ef tools use HostFactoryResolver which throws HostAbortedException internally as a control-flow mechanism to stop the host after discovering the DbContext. 
Your generic catch was swallowing it instead of letting it propagate, so EF saw the process exit abnormally.
***/ 
catch (HostAbortedException)
{
    throw;
}
catch (Exception ex)
{
    Log.Fatal(ex, "Application failed to start.");
    throw;
}
finally
{
    Log.CloseAndFlush();
}

public partial class Program { }