using Adapters.Driven.Persistence;
using Adapters.Driven.Persistence.Context;
using Adapters.Driving.Api.Extensions;
using Application;
using dotenv.net;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Serilog;
using System.IO.Compression;
using System.Text.Json;
using System.Threading.RateLimiting;

// Load .env file (searches up to root directory)
DotEnv.Load(options: new DotEnvOptions(
    probeForEnv: true,
    probeLevelsToSearch: 5
));

// Configure Serilog from appsettings + environment variables
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(new ConfigurationBuilder()
        .AddJsonFile("appsettings.json")
        .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
        .AddEnvironmentVariables()
        .Build())
    .CreateLogger();

try
{
    Log.Information("Starting application");

    var builder = WebApplication.CreateBuilder(args);

    // Add environment variables to configuration
    builder.Configuration.AddEnvironmentVariables();

    // Build connection string from individual env vars if not provided directly
    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
    if (string.IsNullOrEmpty(connectionString))
    {
        var dbHost = Environment.GetEnvironmentVariable("DB_HOST") ?? "localhost";
        var dbPort = Environment.GetEnvironmentVariable("DB_PORT") ?? "1433";
        var dbName = Environment.GetEnvironmentVariable("DB_NAME") ?? "StudentEnrollment";
        var dbUser = Environment.GetEnvironmentVariable("DB_USER") ?? "sa";
        var dbPassword = Environment.GetEnvironmentVariable("DB_PASSWORD") ?? "";

        connectionString = $"Server={dbHost},{dbPort};Database={dbName};User Id={dbUser};Password={dbPassword};TrustServerCertificate=True";
        builder.Configuration["ConnectionStrings:DefaultConnection"] = connectionString;
    }

    builder.Host.UseSerilog();

    // Add services
    builder.Services.AddApplication();
    builder.Services.AddPersistence(builder.Configuration);
    builder.Services.AddGraphQLApi();

    // Response Compression (Brotli preferred, then Gzip)
    builder.Services.AddResponseCompression(options =>
    {
        options.EnableForHttps = true;
        options.Providers.Add<BrotliCompressionProvider>();
        options.Providers.Add<GzipCompressionProvider>();
        options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
            ["application/json", "application/graphql-response+json"]);
    });

    builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
        options.Level = CompressionLevel.Fastest);

    builder.Services.Configure<GzipCompressionProviderOptions>(options =>
        options.Level = CompressionLevel.Fastest);

    // CORS (configurable via CORS_ORIGINS env var, comma-separated)
    var corsOrigins = Environment.GetEnvironmentVariable("CORS_ORIGINS")?.Split(',', StringSplitOptions.RemoveEmptyEntries)
        ?? ["http://localhost:4200"];
    builder.Services.AddCors(options =>
    {
        options.AddDefaultPolicy(policy =>
            policy.WithOrigins(corsOrigins)
                  .AllowAnyHeader()
                  .AllowAnyMethod());
    });

    // Output caching for read-heavy operations
    builder.Services.AddOutputCache(options =>
    {
        options.AddBasePolicy(policy => policy.Expire(TimeSpan.FromSeconds(10)));
        options.AddPolicy("Subjects", policy => policy.Expire(TimeSpan.FromMinutes(5)));
        options.AddPolicy("Professors", policy => policy.Expire(TimeSpan.FromMinutes(5)));
    });

    // Health checks
    builder.Services.AddHealthChecks()
        .AddDbContextCheck<AppDbContext>("database");

    // Rate limiting (DoS prevention)
    builder.Services.AddRateLimiter(options =>
    {
        options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
        options.AddFixedWindowLimiter("graphql", limiter =>
        {
            limiter.PermitLimit = 100;
            limiter.Window = TimeSpan.FromMinutes(1);
            limiter.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
            limiter.QueueLimit = 10;
        });
        options.AddFixedWindowLimiter("mutations", limiter =>
        {
            limiter.PermitLimit = 30;
            limiter.Window = TimeSpan.FromMinutes(1);
            limiter.QueueLimit = 5;
        });
    });

    var app = builder.Build();

    // Verify database connection and apply migrations
    await VerifyDatabaseConnectionAsync(app);

    async Task VerifyDatabaseConnectionAsync(WebApplication app)
    {
        var maxRetries = 10;
        var delay = TimeSpan.FromSeconds(5);

        for (int attempt = 1; attempt <= maxRetries; attempt++)
        {
            try
            {
                using var scope = app.Services.CreateScope();
                var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

                Log.Information("Attempting database connection (attempt {Attempt}/{MaxRetries})...", attempt, maxRetries);

                // Try to apply migrations - this will create the DB if it doesn't exist
                Log.Information("Applying database migrations (this will create DB if needed)...");
                await db.Database.MigrateAsync();
                Log.Information("Database migrations applied successfully");

                // Verify connection works after migrations
                if (await db.Database.CanConnectAsync())
                {
                    Log.Information("Database connection verified successfully");
                    return;
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Database operation failed (attempt {Attempt}/{MaxRetries}): {ErrorMessage}",
                    attempt, maxRetries, ex.Message);

                if (attempt == maxRetries)
                {
                    Log.Fatal("Could not establish database connection after {MaxRetries} attempts. " +
                        "Please verify: DB_HOST={DbHost}, DB_PORT={DbPort}, DB_NAME={DbName}, DB_USER={DbUser}",
                        maxRetries,
                        Environment.GetEnvironmentVariable("DB_HOST") ?? "localhost",
                        Environment.GetEnvironmentVariable("DB_PORT") ?? "1433",
                        Environment.GetEnvironmentVariable("DB_NAME") ?? "StudentEnrollment",
                        Environment.GetEnvironmentVariable("DB_USER") ?? "sa");
                }
            }

            if (attempt < maxRetries)
            {
                Log.Information("Waiting {Delay} seconds before next connection attempt...", delay.TotalSeconds);
                await Task.Delay(delay);
            }
        }
    }

    // Security headers (OWASP recommended)
    app.Use(async (context, next) =>
    {
        var headers = context.Response.Headers;
        headers.Append("X-Content-Type-Options", "nosniff");
        headers.Append("X-Frame-Options", "DENY");
        headers.Append("X-XSS-Protection", "0");
        headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
        headers.Append("Permissions-Policy", "geolocation=(), microphone=(), camera=()");
        // CSP: More restrictive in production, relaxed for dev (GraphQL Playground needs unsafe-inline/eval)
        var csp = app.Environment.IsDevelopment()
            ? "default-src 'self'; " +
              "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +
              "style-src 'self' 'unsafe-inline'; " +
              "img-src 'self' data: https:; " +
              "font-src 'self'; " +
              "connect-src 'self' http://localhost:* https://localhost:*; " +
              "frame-ancestors 'none'; " +
              "base-uri 'self'; " +
              "form-action 'self'"
            : "default-src 'self'; " +
              "script-src 'self'; " +
              "style-src 'self'; " +
              "img-src 'self' data:; " +
              "font-src 'self'; " +
              "connect-src 'self'; " +
              "frame-ancestors 'none'; " +
              "base-uri 'self'; " +
              "form-action 'self'";
        headers.Append("Content-Security-Policy", csp);
        if (!context.Request.IsHttps && !app.Environment.IsDevelopment())
        {
            headers.Append("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
        }
        await next();
    });

    // Request logging (structured logs, excludes sensitive data)
    app.UseSerilogRequestLogging(options =>
    {
        options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
        {
            diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
            diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent.ToString());
        };
        options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
    });

    // Middleware order matters for performance
    app.UseResponseCompression();
    app.UseCors();
    app.UseRateLimiter();
    app.UseOutputCache();

    // Health check endpoint with JSON response
    app.MapHealthChecks("/health", new HealthCheckOptions
    {
        ResponseWriter = async (context, report) =>
        {
            context.Response.ContentType = "application/json";
            var result = new
            {
                status = report.Status.ToString(),
                timestamp = DateTime.UtcNow,
                checks = report.Entries.Select(e => new
                {
                    name = e.Key,
                    status = e.Value.Status.ToString(),
                    description = e.Value.Description,
                    duration = e.Value.Duration.TotalMilliseconds
                })
            };
            await context.Response.WriteAsync(JsonSerializer.Serialize(result, new JsonSerializerOptions
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            }));
        }
    });

    // GraphQL endpoint with Banana Cake Pop playground and rate limiting
    app.MapGraphQL()
        .RequireRateLimiting("graphql")
        .WithOptions(new HotChocolate.AspNetCore.GraphQLServerOptions
        {
            Tool = { Enable = app.Environment.IsDevelopment() }
        });

    await app.RunAsync();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
    await Log.CloseAndFlushAsync();
}
