using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.FileProviders;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using NLog;
using NLog.Web;
using NSwag;
using NSwag.CodeGeneration.TypeScript;
using NSwag.Generation.Processors.Security;
using server.Services;
using TypedSignalR.Client.DevTools;

// Early init of NLog to allow startup and exception logging, before host is built
var logger = NLog.LogManager.Setup()
                      .LoadConfigurationFromAppSettings()
                      .GetCurrentClassLogger();
logger.Debug("Init Main...");

try
{
    var builder = WebApplication.CreateBuilder(args);

    // Services Settings
    // Add services to the container.
    // builder.Services.AddControllersWithViews();

    // NLog: Setup NLog for Dependency injection
    builder.Logging.ClearProviders();
    builder.Host.UseNLog();
    builder.Services.AddEndpointsApiExplorer();

    builder.Services.Configure<FormOptions>(options =>
    {
        options.MultipartBodyLengthLimit = 32 * 1024 * 1024;
    });

    // Add Json.Net Serializer
    builder.Services.AddControllersWithViews().AddNewtonsoftJson(options =>
    {
        // Configure Newtonsoft.Json options here
        options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
        options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
    });

    // Add JWT Token Authorization
    builder.Services
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                RequireExpirationTime = true,
                ValidIssuer = "dlut.edu.cn",
                ValidAudience = "dlut.edu.cn",
                IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes("my secret key 1234567890my secret key 1234567890")),
            };
            options.Authority = $"http://{Global.LocalHost}:5000";
            options.RequireHttpsMetadata = false;

            // We have to hook the OnMessageReceived event in order to
            // allow the JWT authentication handler to read the access
            // token from the query string when a WebSocket or
            // Server-Sent Events request comes in.

            // Sending the access token in the query string is required when using WebSockets or ServerSentEvents
            // due to a limitation in Browser APIs. We restrict it to only calls to the
            // SignalR hub in this code.
            // See https://docs.microsoft.com/aspnet/core/signalr/security#access-token-logging
            // for more information about security considerations when using
            // the query string to transmit the access token.
            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    var accessToken = context.Request.Query["access_token"];

                    // If the request is for our hub...
                    var path = context.HttpContext.Request.Path;
                    if (!string.IsNullOrEmpty(accessToken) && (
                            path.StartsWithSegments("/hubs/JtagHub") ||
                            path.StartsWithSegments("/hubs/ProgressHub")
                        ))
                    {
                        // Read the token out of the query string
                        context.Token = accessToken;
                    }
                    return Task.CompletedTask;
                }
            };
        });
    // Add JWT Token Authorization Policy
    builder.Services.AddAuthorization(options =>
    {
        options.AddPolicy("Admin", policy =>
        {
            policy.RequireClaim(ClaimTypes.Role, new string[] {
                Database.UserPermission.Admin.ToString(),
            });
        });
    });

    // Add CORS policy
    if (builder.Environment.IsDevelopment())
    {
        builder.Services.AddCors(options =>
        {
            options.AddPolicy("Development", policy => policy
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader()
            );
        });
    }
    builder.Services.AddCors(options =>
    {
        options.AddPolicy("Users", policy => policy
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader()
        );
        options.AddPolicy("SignalR", policy => policy
            .WithOrigins("http://localhost:5173")
            .AllowAnyHeader()
            .AllowAnyMethod()
            .AllowCredentials()
        );
    });

    // Use SignalR
    builder.Services.AddSignalR();

    // Add Swagger
    builder.Services.AddSwaggerDocument(options =>
    {
        options.PostProcess = document =>
        {
            document.Info = new NSwag.OpenApiInfo
            {
                Version = "v1",
                Title = "FPGA Web Lab API",
                Description = "Use FPGA in the cloud",
                // TermsOfService = "https://example.com/terms",
                // Contact = new NSwag.OpenApiContact
                // {
                //     Name = "Example Contact",
                //     Url = "https://example.com/contact"
                // },
                // License = new NSwag.OpenApiLicense
                // {
                //     Name = "Example License",
                //     Url = "https://example.com/license"
                // }
            };
        };

        // Authorization
        options.AddSecurity("Bearer", new NSwag.OpenApiSecurityScheme
        {
            Description = "请输入token,格式为 Bearer xxxxxxxx（注意中间必须有空格）",
            Name = "Authorization",
            In = NSwag.OpenApiSecurityApiKeyLocation.Header,
            Type = NSwag.OpenApiSecuritySchemeType.ApiKey,
        });
        options.OperationProcessors.Add(new OperationSecurityScopeProcessor("Bearer"));
    });

    // 添加数据库资源管理器服务
    builder.Services.AddScoped<Database.AppDataConnection>();
    builder.Services.AddScoped<Database.UserManager>();
    builder.Services.AddScoped<Database.ResourceManager>();
    builder.Services.AddScoped<Database.ExamManager>();

    // 添加 HTTP 视频流服务
    builder.Services.AddSingleton<HttpVideoStreamService>();
    builder.Services.AddHostedService(provider => provider.GetRequiredService<HttpVideoStreamService>());
    builder.Services.AddSingleton<HttpHdmiVideoStreamService>();
    builder.Services.AddHostedService(provider => provider.GetRequiredService<HttpHdmiVideoStreamService>());

    // 添加进度跟踪服务
    builder.Services.AddSingleton<ProgressTrackerService>();
    builder.Services.AddHostedService(provider => provider.GetRequiredService<ProgressTrackerService>());

    // Application Settings
    var app = builder.Build();
    // Configure the HTTP request pipeline.
    if (!app.Environment.IsDevelopment())
    {
        // app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();

        // Serve static files
        logger.Info($"Use Static Files : {Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")}");
        app.UseDefaultFiles();
        app.UseStaticFiles(); // Serves files from wwwroot by default

        // Assets Files
        app.UseStaticFiles(new StaticFileOptions
        {
            FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "assets")),
            RequestPath = "/assets"
        });

        // Log Files
        if (!Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "log")))
        {
            Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), "log"));
        }
        app.UseStaticFiles(new StaticFileOptions
        {
            FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "log")),
            RequestPath = "/log"
        });

        // Exam Files (实验静态资源)
        if (Directory.Exists(Path.Combine(Directory.GetCurrentDirectory(), "exam")))
        {
            app.UseStaticFiles(new StaticFileOptions
            {
                FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "exam")),
                RequestPath = "/exam"
            });
        }

        app.MapFallbackToFile("index.html");
    }

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

    // Swagger
    app.UseOpenApi(settings =>
    {
        settings.PostProcess = (document, httpRequest) =>
        {
            document.Servers.Clear();
            document.Servers.Add(new NSwag.OpenApiServer { Url = $"http://{Global.LocalHost}:5000" });
        };
    });
    app.UseSwaggerUi();

    // SignalR
    app.UseWebSockets();
    app.UseSignalRHubSpecification();
    app.UseSignalRHubDevelopmentUI();

    // Router
    app.MapControllers();
    app.MapHub<server.Hubs.JtagHub>("hubs/JtagHub");
    app.MapHub<server.Hubs.ProgressHub>("hubs/ProgressHub");

    // Setup Program
    MsgBus.Init();

    // Generate API Client
    app.MapGet("GetAPIClientCode", async (HttpContext context) =>
    {
        try
        {
            var document = await OpenApiDocument.FromUrlAsync($"http://{Global.LocalHost}:5000/swagger/v1/swagger.json");

            var settings = new TypeScriptClientGeneratorSettings
            {
                ClassName = "{controller}Client",
                UseAbortSignal = false,
                Template = TypeScriptTemplate.Axios,
                TypeScriptGeneratorSettings = {
                },
            };

            var generator = new TypeScriptClientGenerator(document, settings);
            var code = generator.GenerateFile();

            return Results.Text(code, "text/plain; charset=utf-8", Encoding.UTF8);
        }
        catch (Exception err)
        {
            logger.Error(err);
            return Results.Problem(err.ToString());
        }
    }).RequireCors("Development");

    app.Run();
}
catch (Exception exception)
{
    // NLog: catch setup errors
    logger.Error(exception, "Stopped program because of exception");
    throw;
}
finally
{
    // Close UDP Server
    logger.Info("Program is Closing now...");

    // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
    NLog.LogManager.Shutdown();

    // Close Program
    MsgBus.Exit();
}
