using System.Text;
using FastEndpoints;
using FastEndpoints.Swagger;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Npgsql;
using SpaceGame.Api.Universe.Bootstrap;

const string StartupScenarioPath = "scenarios/empty.json";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors((options) =>
{
    options.AddDefaultPolicy((policy) =>
    {
        policy
        .AllowAnyHeader()
        .AllowAnyMethod()
        .AllowAnyOrigin();
    });
});
builder.Services
  .AddOptions<StaticDataOptions>()
  .Bind(builder.Configuration.GetSection("StaticData"))
  .Validate(options => !string.IsNullOrWhiteSpace(options.DataRoot), "StaticData:DataRoot must be configured.")
  .PostConfigure(options =>
  {
      if (Path.IsPathRooted(options.DataRoot))
      {
          options.DataRoot = Path.GetFullPath(options.DataRoot);
          return;
      }

      var candidatePaths = new[]
      {
          Path.GetFullPath(options.DataRoot),
          Path.GetFullPath(Path.Combine(builder.Environment.ContentRootPath, options.DataRoot)),
          Path.GetFullPath(Path.Combine(builder.Environment.ContentRootPath, "..", "..", options.DataRoot)),
      };

      var resolvedPath = candidatePaths.FirstOrDefault(Directory.Exists);
      if (resolvedPath is null)
      {
          throw new InvalidOperationException($"StaticData:DataRoot '{options.DataRoot}' could not be resolved to an existing directory.");
      }

      options.DataRoot = resolvedPath;
  })
  .ValidateOnStart();
builder.Services.Configure<BalanceOptions>(builder.Configuration.GetSection("Balance"));
builder.Services.Configure<OrbitalSimulationOptions>(builder.Configuration.GetSection("OrbitalSimulation"));
builder.Services
  .AddOptions<AuthOptions>()
  .Bind(builder.Configuration.GetSection("Auth"))
  .Validate(options => !string.IsNullOrWhiteSpace(options.ConnectionString), "Auth:ConnectionString must be configured.")
  .ValidateOnStart();
builder.Services
  .AddOptions<JwtOptions>()
  .Bind(builder.Configuration.GetSection("Jwt"))
  .Validate(options => !string.IsNullOrWhiteSpace(options.SigningKey), "Jwt:SigningKey must be configured.")
  .ValidateOnStart();

var jwtOptions = builder.Configuration.GetSection("Jwt").Get<JwtOptions>() ?? new JwtOptions();
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SigningKey));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  .AddJwtBearer(options =>
  {
      options.TokenValidationParameters = new TokenValidationParameters
      {
          ValidateIssuer = true,
          ValidateAudience = true,
          ValidateIssuerSigningKey = true,
          ValidateLifetime = true,
          ValidIssuer = jwtOptions.Issuer,
          ValidAudience = jwtOptions.Audience,
          IssuerSigningKey = signingKey,
          ClockSkew = TimeSpan.FromSeconds(30),
      };
  });
builder.Services
    .AddAuthorizationBuilder()
    .AddPolicy(AuthPolicyNames.AdminAccess, policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireRole(AuthRoleNames.Admin);
    })
    .AddPolicy(AuthPolicyNames.GmAccess, policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireRole(AuthRoleNames.Gm);
    });
builder.Services.AddHttpContextAccessor();

builder.Services.AddSingleton<IBalanceService, BalanceService>();
builder.Services.AddSingleton<AppVersionService>();
builder.Services.AddSingleton<IPlayerStateStore, PlayerStateStore>();
builder.Services.AddSingleton<PlayerFactionProjectionService>();
builder.Services.AddSingleton<LocalPasswordHasher>();
builder.Services.AddSingleton<RefreshTokenFactory>();
builder.Services.AddSingleton<ITokenService, JwtTokenService>();
builder.Services.AddSingleton<IPasswordResetDelivery, DevPasswordResetDelivery>();
builder.Services.AddSingleton<IPlayerIdentityResolver, HttpContextPlayerIdentityResolver>();
builder.Services.AddSingleton((serviceProvider) =>
{
    var authOptions = serviceProvider.GetRequiredService<Microsoft.Extensions.Options.IOptions<AuthOptions>>();
    return new NpgsqlDataSourceBuilder(authOptions.Value.ConnectionString).Build();
});
builder.Services.AddSingleton<IAuthRepository, PostgresAuthRepository>();
builder.Services.AddSingleton<AuthService>();
builder.Services.AddSingleton<AuthSchemaInitializer>();
builder.Services.AddSingleton<DevAuthSeeder>();
builder.Services.AddTransient<SystemGenerationService>();
builder.Services.AddTransient<SpatialBuilder>();
builder.Services.AddTransient<WorldSeedingService>();
builder.Services.AddTransient<ScenarioValidationService>();
builder.Services.AddTransient<ScenarioContentBuilder>();
builder.Services.AddTransient<ScenarioLoader>();
builder.Services.AddTransient<WorldTopologyBuilder>();
builder.Services.AddTransient<WorldRuntimeAssembler>();
builder.Services.AddTransient<WorldBuilder>();
builder.Services.AddSingleton<IStaticDataProvider, StaticDataProvider>();
builder.Services.AddSingleton<WorldService>();
builder.Services.AddSingleton<TelemetryService>();
builder.Services.AddHostedService<SimulationHostedService>();

builder.Services.AddFastEndpoints();
builder.Services.SwaggerDocument();

var app = builder.Build();
await app.Services.GetRequiredService<AuthSchemaInitializer>().EnsureSchemaAsync(CancellationToken.None);
if (builder.Environment.IsDevelopment())
{
    await app.Services.GetRequiredService<DevAuthSeeder>().SeedAsync(CancellationToken.None);
    app.Services.GetRequiredService<WorldService>().LoadFromScenario(StartupScenarioPath);
}

app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseFastEndpoints();
app.UseSwaggerGen();

app.Run();
