using System.Diagnostics;
using Baya.Application.Features.Users.Commands.Create;
using Baya.Application.Models.ApiResult;
using Baya.Application.Models.Identity;
using Baya.Application.ServiceConfiguration;
using Baya.Domain.Entities.User;
using Baya.Infrastructure.CrossCutting.Logging;
using Baya.Infrastructure.CrossCutting.ServiceConfiguration;
using Baya.Infrastructure.Identity.Identity.Dtos;
using Baya.Infrastructure.Identity.Jwt;
using Baya.Infrastructure.Identity.ServiceConfiguration;
using Baya.Infrastructure.Monitoring.Configurations;
using Baya.Infrastructure.Persistence.ServiceConfiguration;
using Baya.SharedKernel.Extensions;
using Baya.Web.Api.Configuration;
using Baya.Web.Plugins.Grpc;
using Baya.WebFramework.Filters;
using Baya.WebFramework.Middlewares;
using Baya.WebFramework.Routing;
using Baya.WebFramework.ServiceConfiguration;
using Baya.WebFramework.Swagger;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Serilog;

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseSerilog(LoggingConfiguration.ConfigureLogger);

var configuration = builder.Configuration;

// Fail fast if a load-bearing secret (DB connection, JWE/field-encryption keys) is missing or still a
// committed placeholder — before any service reaches for it. Skipped in the "Testing" environment.
builder.ValidateRequiredSecrets();

Activity.DefaultIdFormat = ActivityIdFormat.W3C;

// HTTPS metadata is required for the token exchange in deployed environments; relaxed for local
// Development and the Testing host, which run over plain HTTP.
var requireHttpsMetadata = !builder.Environment.IsDevelopment() && !builder.Environment.IsEnvironment("Testing");

builder
    .ConfigureHealthChecks()
    .SetupOpenTelemetry();

builder.Services.Configure<IdentitySettings>(configuration.GetSection(nameof(IdentitySettings)));

var identitySettings = configuration.GetSection(nameof(IdentitySettings)).Get<IdentitySettings>();

builder.Services.AddControllers(options =>
{
    options.Conventions.Add(new RouteTokenTransformerConvention(new SnakeCaseParameterTransformer()));
    options.Filters.Add(typeof(OkResultAttribute));
    options.Filters.Add(typeof(NotFoundResultAttribute));
    options.Filters.Add(typeof(ContentResultFilterAttribute));
    options.Filters.Add(typeof(ModelStateValidationAttribute));
    options.Filters.Add(typeof(BadRequestResultFilterAttribute));
    options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult<Dictionary<string, List<string>>>),
        StatusCodes.Status400BadRequest));
    options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult),
        StatusCodes.Status401Unauthorized));
    options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult),
        StatusCodes.Status403Forbidden));
    options.Filters.Add(new ProducesResponseTypeAttribute(typeof(ApiResult),
        StatusCodes.Status500InternalServerError));

}).ConfigureApiBehaviorOptions(options =>
{
    options.SuppressModelStateInvalidFilter = true;
    options.SuppressMapClientErrors = true;
});


builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwagger("v1","v1.1");


builder.Services.AddApplicationServices()
    .RegisterIdentityServices(identitySettings, requireHttpsMetadata)
    .AddPersistenceServices(configuration)
    .AddCrossCuttingSeams(configuration)
    .AddWebFrameworkServices()
    .AddCorsPolicies(configuration)
    .AddForwardedHeadersConfiguration(configuration)
    .AddRateLimitingPolicies();

// Development-only: capture each OTP in-memory so GET /api/v1/dev/last_otp/{phone} can complete a login
// without an SMS gateway. refinement-phase-8: the capture bridge runs ONLY while the log-only mock SMS sender is
// selected — once a real gateway (Seams:Sms:Provider) ships, the OTP is delivered over the wire and never logged
// or captured. Nothing here is wired in any other environment.
var smsProvider = configuration["Seams:Sms:Provider"];
var usingMockSms = string.IsNullOrWhiteSpace(smsProvider) || smsProvider.Equals("mock", StringComparison.OrdinalIgnoreCase);
if (builder.Environment.IsDevelopment() && usingMockSms)
    builder.Services.AddDevelopmentOtpCapture();

// The IPaymentCaptureSimulator + bookings/convert path is a Development/Testing affordance (b10's real webhook
// confirm supersedes it in production). Re-register the succeeding mock over the production fail-closed stand-in.
if (builder.Environment.IsDevelopment() || builder.Environment.IsEnvironment("Testing"))
    builder.Services.AddDevelopmentPaymentCapture();

builder.Services.RegisterValidatorsAsServices();
builder.Services.AddExceptionHandler<ExceptionHandler>();

builder.Services.AddMapster();

TypeAdapterConfig.GlobalSettings.Scan(typeof(UserCreateCommand).Assembly,
    typeof(GetRolesDto).Assembly);


#region Plugin Services Configuration

builder.Services.ConfigureGrpcPluginServices(builder.Environment);

#endregion



var app = builder.Build();


// Deploy-time migration one-shot (refinement-phase-7): `dotnet run -- migrate` (or `<binary> migrate`) applies
// EF migrations + the idempotent seeders, then exits. Running DDL as a separate deploy step means normal boots —
// especially concurrent multi-instance start-ups — never race on schema, and the runtime login needs no
// permanent DDL rights.
if (args.Any(a => string.Equals(a, "migrate", StringComparison.OrdinalIgnoreCase)))
{
    await app.ApplyMigrationsAsync();
    await app.SeedDefaultUsersAsync();
    if (app.Environment.IsDevelopment())
    {
        await app.SeedPaymentGatewaysAsync();
        await app.SeedDemoWorldAsync();
    }
    return;
}

// Integration tests (WebApplicationFactory, env "Testing") run on in-memory SQLite — the SQL Server
// migrations can't apply there; the test factory does EnsureCreated + seeding itself.
if (!app.Environment.IsEnvironment("Testing"))
{
    if (app.Environment.IsDevelopment())
    {
        // Local convenience: apply migrations + seed on boot. Development-only: a sandbox payment gateway
        // (all-zeros merchant id) and a demo marketplace (nurses/variants/search rows, customers/patients) so the
        // real-path screens aren't empty. Neither belongs in a deployed DB — both are idempotent.
        await app.ApplyMigrationsAsync();
        await app.SeedDefaultUsersAsync();
        await app.SeedPaymentGatewaysAsync();
        await app.SeedDemoWorldAsync();
    }
    else
    {
        // Deployed: DDL is the separate `migrate` step above. Boot only *checks* the schema is current (fail fast
        // on a pending migration) and seeds idempotent runtime data (roles + any configured break-glass admin).
        await app.EnsureSchemaUpToDateAsync();
        await app.SeedDefaultUsersAsync();
    }
}

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
    app.UseExceptionHandler(_=>{});

// First in the pipeline so the resolved client IP (X-Forwarded-For, from a trusted proxy) is in place
// before anything downstream — notably the rate limiter — reads HttpContext.Connection.RemoteIpAddress.
app.UseForwardedHeaders();

app.UseSwaggerAndUi();

app.UseRouting();

// After UseRouting and before the rate limiter / authentication so a pre-flight OPTIONS is answered
// (and not rejected as 429/401) before the browser sends the real cross-origin request.
app.UseCors(CorsServiceExtension.PolicyName);

app.UseRateLimiter();

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

app.UseMetrics()
    .UseHealthChecks();

app.ConfigureGrpcPipeline();

await app.RunAsync();

/// <summary>Exposes the entry point to <c>WebApplicationFactory&lt;Program&gt;</c>-based integration tests.</summary>
public partial class Program;



