using System.Text;
using System.Threading.RateLimiting;
using FluentValidation;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Minio;
using Minio.DataModel.Args;
using Prometheus;
using Serilog;
using StackExchange.Redis;

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

    builder.Host.UseSerilog((ctx, cfg) => cfg.ReadFrom.Configuration(ctx.Configuration));

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

    // Database
    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));

    // Redis
    builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
    {
        var config = ConfigurationOptions.Parse(
            sp.GetRequiredService<IConfiguration>()["Redis:ConnectionString"]!);
        config.AbortOnConnectFail = false;
        return ConnectionMultiplexer.Connect(config);
    });

    // MinIO
    var minioOptions = builder.Configuration.GetSection(MinioOptions.Section).Get<MinioOptions>()!;
    builder.Services.AddSingleton<IMinioClient>(new MinioClient()
        .WithEndpoint(minioOptions.Endpoint)
        .WithCredentials(minioOptions.AccessKey, minioOptions.SecretKey)
        .WithSSL(minioOptions.UseSsl)
        .Build());

    // Site Configuration
    builder.Services.Configure<SiteConfigOptions>(
    builder.Configuration.GetSection(SiteConfigOptions.Section));

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

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

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

    var ocrOptions = builder.Configuration.GetSection(OcrOptions.Section).Get<OcrOptions>();
    
    if (ocrOptions?.Enabled == true)
    {
        builder.Services.AddSingleton<ImagePreprocessor>();

        if (ocrOptions.Provider == "azure")
            builder.Services.AddScoped<IOcrService, AzureDocumentOcrService>();
        else
            builder.Services.AddScoped<IOcrService, TesseractOcrService>();

        builder.Services.AddScoped<OcrDraftPreFiller>();
        builder.Services.AddHostedService<OcrProcessingService>();
    }

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

    if (string.IsNullOrEmpty(jwtOptions.Secret) ||
        Encoding.UTF8.GetByteCount(jwtOptions.Secret) < 32)
        throw new InvalidOperationException(
            "JWT Secret must be at least 256 bits (32 bytes). " +
            "Configure a strong secret via Jwt:Secret in appsettings or the Jwt__Secret environment variable.");

    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.Secret))
            };
        });
    builder.Services.AddAuthorization();

    // CORS
    builder.Services.AddCors(options =>
    {
        options.AddPolicy("VigilCare", policy =>
        {
            policy.WithOrigins(builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>()!)
                  .AllowAnyHeader()
                  .AllowAnyMethod()
                  .AllowCredentials();
        });
    });

    // Rate limiting (disabled in Testing — integration tests issue many auth requests)
    var isTesting = builder.Environment.IsEnvironment("Testing");
    if (!isTesting)
    {
        builder.Services.AddRateLimiter(options =>
        {
            options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
            options.AddFixedWindowLimiter("auth", opt =>
            {
                opt.Window = TimeSpan.FromMinutes(5);
                opt.PermitLimit = 10;
                opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
                opt.QueueLimit = 0;
            });
        });
    }

    // Services
    builder.Services.AddScoped<IAuthService, AuthService>();
    builder.Services.AddScoped<IBatchService, BatchService>();
    builder.Services.AddScoped<IDraftService, DraftService>();
    builder.Services.AddScoped<IDocumentStorageService, DocumentStorageService>();
    builder.Services.AddScoped<IVerificationService, VerificationService>();
    builder.Services.AddScoped<IWorkQueueService, WorkQueueService>();
    builder.Services.AddScoped<IPatientRegistryService, PatientRegistryService>();
    builder.Services.AddScoped<IUserDirectoryService, UserDirectoryService>();
    builder.Services.AddScoped<IPromotionService, PromotionService>();
    builder.Services.AddScoped<IIdempotencyService, IdempotencyService>();
    builder.Services.AddScoped<IMrnGenerator, MrnGenerator>();
    builder.Services.AddScoped<IDigitizationHistoryService, DigitizationHistoryService>();
    builder.Services.AddScoped<IAttestationService, AttestationService>();
    builder.Services.AddScoped<ILiveCaptureService, LiveCaptureService>();
    builder.Services.AddScoped<IBatchEventService, BatchEventService>();
    builder.Services.AddScoped<ICoverSheetService, CoverSheetService>();
    builder.Services.AddScoped<IFhirService, FhirService>();
    
    builder.Services.AddHostedService<MetricsCollectorService>();
    builder.Services.AddHostedService<PromotionRetryService>();

    // Health checks
    builder.Services.AddHealthChecks()
        .AddNpgSql(
            builder.Configuration.GetConnectionString("DefaultConnection")!,
            name: "postgresql",
            tags: new[] { "ready", "startup" })
        .AddRedis(
            builder.Configuration["Redis:ConnectionString"]!,
            name: "redis",
            tags: new[] { "ready" })
        .AddCheck<MinioHealthCheck>(
            "minio",
            tags: new[] { "ready" });

    builder.Services.AddValidatorsFromAssemblyContaining<Program>();
    builder.Services.AddScoped<ValidationFilter>();
    builder.Services.AddControllers(options =>
    {
        options.OutputFormatters.Insert(0, new FhirJsonOutputFormatter());
        options.Filters.AddService<ValidationFilter>();
    });
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddVigilCareRecordsSwagger();

    var app = builder.Build();

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

    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI(options =>
        {
            options.SwaggerEndpoint("/swagger/v1/swagger.json", "VigilCare Records API v1");
            options.DocumentTitle = "VigilCare Records API";
        });
    }

    app.UseHttpMetrics();
    app.UseCors("VigilCare");
    if (!app.Environment.IsEnvironment("Testing"))
        app.UseRateLimiter();
    app.UseAuthentication();
    app.UseAuthorization();
    app.MapControllers();
    app.MapMetrics();

    app.MapHealthChecks("/health/live", new HealthCheckOptions
    {
        Predicate = _ => false,
        ResultStatusCodes =
        {
            [HealthStatus.Healthy] = StatusCodes.Status200OK,
            [HealthStatus.Degraded] = StatusCodes.Status200OK,
            [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
        }
    });

    app.MapHealthChecks("/health/ready", new HealthCheckOptions
    {
        Predicate = check => check.Tags.Contains("ready"),
        ResultStatusCodes =
        {
            [HealthStatus.Healthy] = StatusCodes.Status200OK,
            [HealthStatus.Degraded] = StatusCodes.Status200OK,
            [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
        }
    });

    app.MapHealthChecks("/health/startup", new HealthCheckOptions
    {
        Predicate = check => check.Tags.Contains("startup"),
        ResultStatusCodes =
        {
            [HealthStatus.Healthy] = StatusCodes.Status200OK,
            [HealthStatus.Degraded] = StatusCodes.Status200OK,
            [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
        }
    });

    if (!app.Environment.IsEnvironment("Testing"))
    {
        using var scope = app.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await db.Database.MigrateAsync();
        await DataSeeder.SeedAsync(db);

        var minio = scope.ServiceProvider.GetRequiredService<IMinioClient>();
        var minioOpts = scope.ServiceProvider.GetRequiredService<IOptions<MinioOptions>>().Value;
        if (!await minio.BucketExistsAsync(new BucketExistsArgs().WithBucket(minioOpts.BucketName)))
            await minio.MakeBucketAsync(new MakeBucketArgs().WithBucket(minioOpts.BucketName));
    }

    app.Run();
}
catch (System.Exception)
{
    
    throw;
}


public partial class Program { }