using MemberCenter.Infrastructure.Configuration;
using MemberCenter.Infrastructure.Identity;
using MemberCenter.Infrastructure.Persistence;
using MemberCenter.Infrastructure.Services;
using MemberCenter.Application.Abstractions;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using OpenIddict.Abstractions;
using OpenIddict.Server.AspNetCore;
using OpenIddict.Validation.AspNetCore;

EnvLoader.LoadDotEnvIfDevelopment();

var builder = WebApplication.CreateBuilder(args);
var pathBase = NormalizePathBase(builder.Configuration["PathBase"]);
var issuer = builder.Configuration["Auth:Issuer"];
var issuerUri = ParseAbsoluteUriOrThrow(issuer, "Auth:Issuer");
var allowInsecureHttp = builder.Configuration.GetValue("Auth:AllowInsecureHttp", false);

builder.Services.AddDbContext<MemberCenterDbContext>(options =>
{
    var connectionString = builder.Configuration.GetConnectionString("Default")
        ?? "Host=localhost;Database=member_center;Username=postgres;Password=postgres";

    options.UseNpgsql(connectionString);
    options.UseOpenIddict();
});

builder.Services
    .AddIdentity<ApplicationUser, ApplicationRole>(options =>
    {
        options.User.RequireUniqueEmail = true;
        options.Password.RequireDigit = true;
        options.Password.RequireLowercase = true;
        options.Password.RequireUppercase = true;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequiredLength = 8;
    })
    .AddEntityFrameworkStores<MemberCenterDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme;
});

builder.Services.AddOpenIddict()
    .AddCore(options =>
    {
        options.UseEntityFrameworkCore()
            .UseDbContext<MemberCenterDbContext>();
    })
    .AddServer(options =>
    {
        options.SetAuthorizationEndpointUris(WithPathBase(pathBase, "/oauth/authorize"));
        options.SetTokenEndpointUris(
            WithPathBase(pathBase, "/oauth/token"),
            WithPathBase(pathBase, "/auth/login"),
            WithPathBase(pathBase, "/auth/refresh"));
        options.SetLogoutEndpointUris(WithPathBase(pathBase, "/auth/logout"));
        if (issuerUri is not null)
        {
            options.SetIssuer(issuerUri);
        }

        options.AllowAuthorizationCodeFlow()
            .RequireProofKeyForCodeExchange();
        options.AllowRefreshTokenFlow();
        options.AllowPasswordFlow();
        options.AllowClientCredentialsFlow();

        options.AcceptAnonymousClients();

        options.RegisterScopes(
            OpenIddictConstants.Scopes.OpenId,
            OpenIddictConstants.Scopes.Email,
            OpenIddictConstants.Scopes.Profile,
            "newsletter:list.read",
            "newsletter:send.write",
            "newsletter:send.read",
            "newsletter:events.read",
            "newsletter:events.write",
            "newsletter:events.write.global");

        options.AddDevelopmentEncryptionCertificate();
        options.AddDevelopmentSigningCertificate();
        options.DisableAccessTokenEncryption();

        var aspNetCore = options.UseAspNetCore()
            .EnableAuthorizationEndpointPassthrough()
            .EnableTokenEndpointPassthrough()
            .EnableLogoutEndpointPassthrough()
            .EnableStatusCodePagesIntegration();

        if (builder.Environment.IsDevelopment() || allowInsecureHttp)
        {
            // Allows OIDC/OAuth endpoints to operate behind non-HTTPS internal networks/proxies.
            aspNetCore.DisableTransportSecurityRequirement();
        }
    })
    .AddValidation(options =>
    {
        options.UseLocalServer();
        options.UseAspNetCore();
    });

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Admin", policy => policy.RequireRole("admin"));
});

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});

builder.Services.AddControllers();
builder.Services.AddScoped<INewsletterService, NewsletterService>();
builder.Services.AddScoped<IEmailBlacklistService, EmailBlacklistService>();
builder.Services.AddScoped<ITenantService, TenantService>();
builder.Services.AddScoped<INewsletterListService, NewsletterListService>();
builder.Services.AddScoped<IAccountProvisioningService, AccountProvisioningService>();
builder.Services.Configure<SendEngineWebhookOptions>(builder.Configuration.GetSection("SendEngine"));
builder.Services.AddHttpClient<SendEngineWebhookPublisher>();
builder.Services.AddScoped<ISendEngineWebhookPublisher, SendEngineWebhookPublisher>();

var app = builder.Build();

app.UseForwardedHeaders();
if (!string.IsNullOrWhiteSpace(pathBase))
{
    app.UsePathBase(pathBase);
}

app.Use(async (context, next) =>
{
    if (issuerUri is not null && IsOpenIddictRequest(context.Request.Path))
    {
        context.Request.Scheme = issuerUri.Scheme;
        context.Request.Host = HostString.FromUriComponent(issuerUri);
    }

    await next();
});

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

app.MapControllers();

app.Run();

static string? NormalizePathBase(string? pathBase)
{
    if (string.IsNullOrWhiteSpace(pathBase))
    {
        return null;
    }

    var normalized = pathBase.StartsWith('/') ? pathBase : $"/{pathBase}";
    return normalized.Length > 1 ? normalized.TrimEnd('/') : normalized;
}

static string WithPathBase(string? pathBase, string relativePath)
{
    var normalizedRelativePath = relativePath.StartsWith('/') ? relativePath : $"/{relativePath}";
    return string.IsNullOrWhiteSpace(pathBase)
        ? normalizedRelativePath
        : $"{pathBase}{normalizedRelativePath}";
}

static Uri? ParseAbsoluteUriOrThrow(string? uri, string configKey)
{
    if (string.IsNullOrWhiteSpace(uri))
    {
        return null;
    }

    if (!Uri.TryCreate(uri, UriKind.Absolute, out var parsed))
    {
        throw new InvalidOperationException($"{configKey} must be an absolute URI.");
    }

    return parsed;
}

static bool IsOpenIddictRequest(PathString path)
{
    return path.StartsWithSegments("/.well-known", StringComparison.OrdinalIgnoreCase)
        || path.StartsWithSegments("/oauth", StringComparison.OrdinalIgnoreCase)
        || path.StartsWithSegments("/auth", StringComparison.OrdinalIgnoreCase);
}
