using FrameProcessor.Concurrency;
using FrameProcessor.Configuration;
using FrameProcessor.ImagePipeline;
using FrameProcessor.Middleware;
using FrameProcessor.Mqtt;
using FrameProcessor.Storage;
using FrameProcessor.UrlFetch;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi;
using Serilog;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddJsonFile("frames.json", optional: false, reloadOnChange: true);

builder.Host.UseSerilog((context, services, configuration) => configuration
    .ReadFrom.Configuration(context.Configuration)
    .ReadFrom.Services(services)
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .WriteTo.File(
        path: "logs/frame-processor-.log",
        rollingInterval: RollingInterval.Day));

builder.Services.AddControllers();

builder.Services.AddOpenApi(options =>
{
    options.AddDocumentTransformer((document, context, _) =>
    {
        document.Info.Title = "Frame Processor";
        document.Info.Version = "v1";

        document.Components ??= new OpenApiComponents();
        document.Components.SecuritySchemes ??= new Dictionary<string, IOpenApiSecurityScheme>();
        document.Components.SecuritySchemes["ApiKey"] = new OpenApiSecurityScheme
        {
            Type = SecuritySchemeType.ApiKey,
            In = ParameterLocation.Header,
            Name = "X-Api-Key",
            Description = "Shared API key required for /api/* endpoints.",
        };

        document.Security =
        [
            new OpenApiSecurityRequirement
            {
                [new OpenApiSecuritySchemeReference("ApiKey", document)] = new List<string>(),
            },
        ];
        return Task.CompletedTask;
    });
});

builder.Services.AddOptions<MqttOptions>()
    .Bind(builder.Configuration.GetSection(MqttOptions.SectionName))
    .ValidateDataAnnotations()
    .ValidateOnStart();

builder.Services.AddOptions<StorageOptions>()
    .Bind(builder.Configuration.GetSection(StorageOptions.SectionName))
    .ValidateDataAnnotations()
    .ValidateOnStart();

builder.Services.AddOptions<UrlFetchOptions>()
    .Bind(builder.Configuration.GetSection(UrlFetchOptions.SectionName))
    .ValidateDataAnnotations()
    .ValidateOnStart();

builder.Services.AddOptions<ApiKeyOptions>()
    .Configure<IConfiguration>((opts, cfg) => opts.Value = cfg[ApiKeyOptions.SectionName] ?? string.Empty)
    .ValidateDataAnnotations()
    .ValidateOnStart();

// FramesOptions is bound but not validated via the options pipeline so that
// IOptionsMonitor<FramesOptions> can fire OnChange with invalid content during
// hot-reload without throwing. FramesRegistry takes responsibility for both
// strict startup validation (in its constructor) and lenient reload validation.
builder.Services.AddOptions<FramesOptions>()
    .Bind(builder.Configuration);
builder.Services.AddSingleton<FramesOptionsValidator>();
builder.Services.AddSingleton<FramesRegistry>();
builder.Services.AddSingleton<IImagePipeline, FrameProcessor.ImagePipeline.ImagePipeline>();
builder.Services.AddSingleton<ImageStore>();
builder.Services.AddSingleton<MqttPublisher>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<MqttPublisher>());
builder.Services.AddSingleton<FrameLockProvider>();

builder.Services.AddHttpClient<IImageUrlFetcher, ImageUrlFetcher>((sp, client) =>
{
    var opts = sp.GetRequiredService<IOptions<UrlFetchOptions>>().Value;
    client.Timeout = TimeSpan.FromSeconds(opts.TimeoutSeconds);
})
.ConfigurePrimaryHttpMessageHandler(sp =>
{
    var opts = sp.GetRequiredService<IOptions<UrlFetchOptions>>().Value;
    return new HttpClientHandler
    {
        AllowAutoRedirect = true,
        MaxAutomaticRedirections = opts.MaxRedirects,
    };
});

var app = builder.Build();

// Eagerly resolve FramesRegistry so an invalid frames.json fails startup fast.
_ = app.Services.GetRequiredService<FramesRegistry>();

app.MapOpenApi();
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint("/openapi/v1.json", "Frame Processor v1");
    options.RoutePrefix = "swagger";
});

app.UseMiddleware<ApiKeyMiddleware>();

app.MapControllers();

app.Run();
