using BoredCareers.Controllers.Payment;
using BoredCareers.Services;
using BoredCareers.Services.DatabaseService;
using System.Threading.RateLimiting;
using Stripe;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using BoredCareers.Services.TimerService;
using BoredCareers.Entities;

var builder = WebApplication.CreateBuilder(args);

// Disable null warnings becuse string.IsNullOrEmpty checks for NULL or Empty
#pragma warning disable CS8604

////////////////////////////////
/////// Database Service ///////
////////////////////////////////

// Address
string? _dbserver = Environment.GetEnvironmentVariable("MySQLServer");
string dbserver = !string.IsNullOrEmpty(_dbserver) ? _dbserver : "localhost";

// Database
string? _dbdatabase = Environment.GetEnvironmentVariable("MySQLDatabase");
string dbdatabase = !string.IsNullOrEmpty(_dbdatabase) ? _dbdatabase : "boredcareers";

// UserName
string? _dbuser = Environment.GetEnvironmentVariable("MySQLUser");
string dbUser = !string.IsNullOrEmpty(_dbuser) ? _dbuser : "root"; 

// Password
string? _dbpass = Environment.GetEnvironmentVariable("MySQLPass");
string dbPass = !string.IsNullOrEmpty(_dbpass) ? _dbpass : "oasv34$8gpv023dd";

// Create the database serivice
builder.Services.AddSingleton<DatabaseService>(sp =>
    new DatabaseService("server=" + dbserver + ";user=" + dbUser + ";database=" + dbdatabase + ";password=" + dbPass + ";port=3306;OldGuids=true;")
);

////////////////////////////////
///////// Email Service ////////
////////////////////////////////

// Address
string? _eServer = Environment.GetEnvironmentVariable("EmailServer");
string EmailServer = !string.IsNullOrEmpty(_eServer) ? _eServer : "mail.mistox.com";

// Port
string? _ePort = Environment.GetEnvironmentVariable("EmailPort");
int EmailPort = !string.IsNullOrEmpty(_ePort) ? Convert.ToInt32(_ePort) : 587;

// User
string? _eAddress = Environment.GetEnvironmentVariable("EmailAddress");
string EmailAddress = !string.IsNullOrEmpty(_eAddress) ? _eAddress : "no-reply@mistox.com";

// Password
string? _ePassword = Environment.GetEnvironmentVariable("EmailPassword");
string EmailPassword = !string.IsNullOrEmpty(_ePassword) ? _ePassword : "";

// Create the email service
EmailService Emailservice = new EmailService( EmailServer, EmailPort, EmailAddress, EmailPassword );
builder.Services.Add( new ServiceDescriptor( typeof( EmailService ), Emailservice ));

////////////////////////////////
/////// Payment Service ////////
////////////////////////////////

// Payment service name -> must be name of PaymentType enum
string? PaymentService = Environment.GetEnvironmentVariable("PaymentService");
IPayment._PaymentType = (PaymentType)Enum.Parse(typeof(PaymentType), PaymentService, true);

if (IPayment._PaymentType == PaymentType.StripeIntent) {
    // Get PublicKey
    string? StripePublicKey = Environment.GetEnvironmentVariable("StripePublicKey");
    IPayment._PublicKey = string.IsNullOrEmpty(StripePublicKey) ? "" : StripePublicKey;
    // Get PrivateKey
    string? StripeAPIKey = Environment.GetEnvironmentVariable("StripeApiKey");
    StripeConfiguration.ApiKey = StripeAPIKey;
    // Get Endpoint secret
    string? StripeEndpointKey = Environment.GetEnvironmentVariable("StripeEndpointSecret");
    IPayment._EndpointSecret = string.IsNullOrEmpty(StripeEndpointKey) ? "" : StripeEndpointKey;
}

////////////////////////////////
///////   Auth Service  ////////
////////////////////////////////

RsaSecurityKey? PublicKey = null;
using (HttpClient client = new HttpClient()) {
    while (PublicKey == null) {
        try {
            HttpResponseMessage PublicKeyResponse = await client.GetAsync("https://auth.mistox.com/api/auth/publickey");
            if (PublicKeyResponse.IsSuccessStatusCode) {
                string publicKey = await PublicKeyResponse.Content.ReadAsStringAsync();
                RSA rsa = RSA.Create();
                rsa.ImportFromPem(publicKey);
                PublicKey = new RsaSecurityKey(rsa);
            } else {
                await Task.Delay(2000); // sleep the main thread for 2 seconds before sending another request. Prevent DDOS of my own equiptment
                Console.WriteLine("auth.mistox.com returned error code: " + PublicKeyResponse.StatusCode);
            }
        } catch (Exception e) {
            await Task.Delay(2000); // sleep the main thread for 2 seconds before sending another request. Prevent DDOS of my own equiptment
            Console.WriteLine("Error loading public key: " + e.InnerException?.Message);
        }
    }
    Console.WriteLine("PublicKey loaded");
}

builder.Services.AddAuthentication(options => {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => {
    options.TokenValidationParameters = new TokenValidationParameters {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = "https://auth.mistox.com",
        ValidAudience = "mistox-llc-auth-token",
        IssuerSigningKey = PublicKey,
        ClockSkew = TimeSpan.FromMinutes(1)
    };
    options.Events = new JwtBearerEvents {
        OnMessageReceived = context => {
            context.Token = context.Request.Cookies["mistox_session"];
            return Task.CompletedTask;
        }
    };
});

////////////////////////////////
///  Rate Limiting Service  ////
////////////////////////////////

List<string> allowedOrigins = new List<string>{ "https://boredcareers.com", "https://www.boredcareers.com" };
if (builder.Environment.IsDevelopment()) {
    allowedOrigins.Add("http://localhost:5000");
}

builder.Services.AddCors(options => {
    options.AddDefaultPolicy(policy => {
        policy.WithOrigins(allowedOrigins.ToArray())
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials();
    });
});

builder.Services.AddRateLimiter(options => {
    options.AddPolicy("PerUserPolicy", httpContext => {
        var userId = httpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
            ?? $"ip:{httpContext.Connection.RemoteIpAddress}";

        return RateLimitPartition.GetTokenBucketLimiter(userId, key => new TokenBucketRateLimiterOptions {
            TokenLimit = 10,                 // max 10 requests
            QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
            QueueLimit = 0,
            ReplenishmentPeriod = TimeSpan.FromSeconds(15),
            TokensPerPeriod = 2,
            AutoReplenishment = true
        });
    });
});

////////////////////////////////
///// Background Services  /////
////////////////////////////////

builder.Services.AddHostedService<JobCleanupService>();

////////////////////////////////
///// ASPNET Core Function /////
////////////////////////////////

builder.Services.AddControllers();

var app = builder.Build();

// Configure the HTTP request pipeline.
if( !app.Environment.IsDevelopment() ) {
    app.UseHsts();
}

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

app.UseRateLimiter();

app.UseCors();

app.UseRouting();

app.UseAuthentication();

app.Use(async (context, next) =>{
    ClaimsPrincipal user = context.User;
    if (user.Identity?.IsAuthenticated == true) {
        string? token = context.Request.Cookies["mistox_session"];
        Claim? expClaim = user.FindFirst(JwtRegisteredClaimNames.Exp);
        if (expClaim != null && long.TryParse(expClaim.Value, out long expUnix)) {
            DateTimeOffset expTime = DateTimeOffset.FromUnixTimeSeconds(expUnix);
            if ((expTime - DateTimeOffset.UtcNow) < TimeSpan.FromDays(3)) {
                using (HttpClient client = new HttpClient()) {
                    HttpResponseMessage response = await client.PostAsJsonAsync("https://auth.mistox.com/api/auth/renew", new JWTRenewRequest() { JWT = token });
                    if (response.IsSuccessStatusCode) {
                        string newJwt = await response.Content.ReadAsStringAsync();
                        context.Response.Cookies.Append("mistox_session", newJwt, new CookieOptions {
                            HttpOnly = true,
                            Secure = true,
                            SameSite = SameSiteMode.Strict,
                            Expires = DateTimeOffset.UtcNow.AddYears(3)
                        });
                    }
                }
            }
        }
    }

    await next();
});

app.MapControllers();

app.MapFallbackToFile("index.html");

app.Run();
