using GameList.Data;
using GameList.Endpoints;
using GameList.Infrastructure;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using System.Net;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateBuilder(args);

var dataDirectory = Path.Combine(builder.Environment.ContentRootPath, "App_Data");
Directory.CreateDirectory(dataDirectory);

var dataProtectionDirectory = Path.Combine(dataDirectory, "keys");
Directory.CreateDirectory(dataProtectionDirectory);

var configuredConnection = builder.Configuration.GetConnectionString("Default");
var dbPath = Path.Combine(dataDirectory, "gamelist.db");
var connectionBuilder = new SqliteConnectionStringBuilder(string.IsNullOrWhiteSpace(configuredConnection) ? $"Data Source={dbPath}" : configuredConnection);

if (connectionBuilder.DataSource.Contains("App_Data", StringComparison.OrdinalIgnoreCase))
{
    var fileName = Path.GetFileName(connectionBuilder.DataSource);
    connectionBuilder.DataSource = Path.Combine(dataDirectory, fileName);
}
else if (!Path.IsPathRooted(connectionBuilder.DataSource))
{
    connectionBuilder.DataSource = Path.GetFullPath(connectionBuilder.DataSource, dataDirectory);
}

var connectionString = connectionBuilder.ToString();

builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlite(connectionString));
builder.Services.AddScoped<SuggestionWorkflowService>();
builder.Services.AddScoped<VoteWorkflowService>();
builder.Services.AddScoped<AdminWorkflowService>();
builder.Services.AddScoped<ResultsWorkflowService>();

builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); });

builder.Services.AddHttpClient("imageValidation").ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AllowAutoRedirect = false });
builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(dataProtectionDirectory));

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
    options.Cookie.Name = PlayerIdentityExtensions.PlayerCookieName;
    options.Cookie.HttpOnly = true;
    options.Cookie.SameSite = SameSiteMode.Strict;
    options.Cookie.SecurePolicy = builder.Environment.IsDevelopment() ? CookieSecurePolicy.SameAsRequest : CookieSecurePolicy.Always;
    options.SlidingExpiration = true;
    options.ExpireTimeSpan = TimeSpan.FromDays(30);
    options.Events = new CookieAuthenticationEvents
    {
        OnRedirectToLogin = ctx =>
        {
            ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return Task.CompletedTask;
        },
        OnRedirectToAccessDenied = ctx =>
        {
            ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return Task.CompletedTask;
        }
    };
});

builder.Services.AddAuthorization(options => { options.AddPolicy(PlayerIdentityExtensions.AdminPolicy, policy => policy.RequireClaim(PlayerIdentityExtensions.AdminClaim, "true")); });

var app = builder.Build();

app.UseForwardedHeaders(BuildForwardedHeadersOptions(builder.Configuration));

var basePath = builder.Configuration["BasePath"];
if (!string.IsNullOrWhiteSpace(basePath))
{
    app.UsePathBase(basePath);
    UpdateIndexMetaBase(app.Environment, basePath);
}

app.UseGlobalExceptionLogging();
app.UseAuthentication();
app.UseMiddleware<EnsurePlayerExistsMiddleware>();
app.UseAuthorization();

// Ensure database and migrations are applied on startup
using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    db.Database.Migrate();
}

app.UseDefaultFiles();
app.UseStaticFiles();

app.MapHealthChecks();
app.MapAuthEndpoints();
app.MapStateEndpoints();
app.MapSuggestEndpoints();
app.MapVoteEndpoints();
app.MapResultsEndpoints();
app.MapAdminEndpoints();

app.Run();

static ForwardedHeadersOptions BuildForwardedHeadersOptions(IConfiguration config)
{
    var options = new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost
    };

    options.KnownIPNetworks.Clear();
    options.KnownProxies.Clear();

    foreach (var proxy in config.GetSection("ForwardedHeaders:KnownProxies").Get<string[]>() ?? [])
    {
        if (IPAddress.TryParse(proxy, out var parsedProxy))
            options.KnownProxies.Add(parsedProxy);
    }

    foreach (var network in config.GetSection("ForwardedHeaders:KnownNetworks").Get<string[]>() ?? [])
    {
        var parts = network.Split('/', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
        if (parts.Length != 2)
            continue;

        if (!IPAddress.TryParse(parts[0], out var prefix))
            continue;

        if (!int.TryParse(parts[1], out var prefixLength))
            continue;

        options.KnownIPNetworks.Add(new System.Net.IPNetwork(prefix, prefixLength));
    }

    return options;
}

static void UpdateIndexMetaBase(IWebHostEnvironment env, string basePath)
{
    try
    {
        var indexPath = Path.Combine(env.WebRootPath, "index.html");
        if (!File.Exists(indexPath))
            return;

        var text = File.ReadAllText(indexPath);
        var marker = "name=\"app-base\"";
        var contentKey = "content=\"";
        var markerIndex = text.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
        if (markerIndex < 0)
            return;

        var contentIndex = text.IndexOf(contentKey, markerIndex, StringComparison.OrdinalIgnoreCase);
        if (contentIndex < 0)
            return;

        var valueStart = contentIndex + contentKey.Length;
        var valueEnd = text.IndexOf('"', valueStart);
        if (valueEnd < 0)
            return;

        var current = text[valueStart..valueEnd];
        var normalized = basePath.EndsWith('/') ? basePath.TrimEnd('/') : basePath;
        if (current == normalized)
            return;

        var updated = text[..valueStart] + normalized + text[valueEnd..];
        File.WriteAllText(indexPath, updated);
    }
    catch
    {
        // If we can't rewrite, continue; frontend can still be set manually.
    }
}

public partial class Program;
