// Copyright © Selmo Technology GmbH. All Rights Reserved

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Novus.Connectivity.Common;
using Novus.Shared.Types.IssueHandling;
using Radzen;
using SelmoStudio.Hmi.Common.SelmoProject;
using SelmoStudio.Hmi.Common.SelmoProject.ProcessValue;
using SelmoStudio.Hmi.Plugin.Services;
using SelmoStudio.Hmi.Server.Alarms.Service;
using SelmoStudio.Hmi.Server.AuditTrail.Service;
using SelmoStudio.Hmi.Server.Db.Authentication;
using SelmoStudio.Hmi.Server.Db.Authentication.Service;
using SelmoStudio.Hmi.Server.Db.Context;
using SelmoStudio.Hmi.Server.Diagnostic;
using SelmoStudio.Hmi.Server.Languages.Factory;
using SelmoStudio.Hmi.Server.Languages.Service;
using SelmoStudio.Hmi.Server.ProcessValue.Model;
using SelmoStudio.Hmi.Server.ProcessValue.Services;
using SelmoStudio.Hmi.Server.SelmoHmi.Authentication.Service;
using SelmoStudio.Hmi.Server.SelmoHmi.Connection.Service;
using SelmoStudio.Hmi.Server.SelmoHmi.DataRecords.Service;
using SelmoStudio.Hmi.Server.SelmoHmi.Dialog.Service;
using SelmoStudio.Hmi.Server.SelmoHmi.Helpers;
using SelmoStudio.Hmi.Server.SelmoHmi.Notification.Service;
using SelmoStudio.Hmi.Server.SelmoHmi.ProjectInformation.Service;
using SelmoStudio.Hmi.Server.SelmoHmi.Sequence.Service;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.Service;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.TestImpl;
using SelmoStudio.Hmi.Server.SelmoHmi.Shared.WellKnownTypes;
using SelmoStudio.Hmi.Server.Service;
using SelmoStudio.Hmi.Server.Shared.Service;
using SelmoStudio.Hmi.Server.Startup;
using SelmoStudio.Hmi.Server.TestSetup.Services;
using Serilog;
using System.Reflection;
using System.Text;

const string PolicyAllowSpecificOrigins = "PolicyAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

ConfigureDatabase(builder);

ConfigureLogging(builder);//, project);
ConfigureAuthentication(builder);
ConfigureAuthorization(builder);
ConfigureDefaultPolicy(builder);

RegisterSwagger(builder);
RegisterServices(builder);

var address = "https://localhost:7226";
//try
//{
//    address = builder.Configuration["JWT:ValidIssuer"] ?? "https://localhost:7226";
//}
//catch (Exception)
//{
//}

builder.Services.AddHttpClient<IPluginAuthenticationService, PluginAuthenticationService>(client =>
{
    client.BaseAddress = new Uri(address);
    client.Timeout = TimeSpan.FromSeconds(30);
    client.DefaultRequestHeaders.TryAddWithoutValidation("Service", Assembly.GetAssembly(typeof(Program))?.GetName().Name);
    //client.BaseAddress = new Uri("https://localhost:7226");
});

builder.Services.AddScoped<AuthenticationStateProvider, SelmoAuthenticationStateProvider>();

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo("Keys"));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddResponseCaching();
builder.Services.AddLocalization();
builder.Services.AddControllersWithViews();
builder.Services.AddHttpClient();
builder.Services.AddHttpContextAccessor();
builder.Services.AddSession();
builder.Logging.AddConsole();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/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();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();
app.UseSession();

app.MapBlazorHub(options =>
{
    options.WebSockets.CloseTimeout = TimeSpan.FromSeconds(60);
});

app.MapFallbackToPage("/_Host");

app.UseAuthentication();
app.UseAuthorization();

var log = app.Services.GetRequiredService<ILogger<Program>>();

await InitSetupData(app, log);// ,project);
InitServiceData(app, log);

app.UseCors(PolicyAllowSpecificOrigins);

app.MapControllers();

app.Run();

static void ConfigureDatabase(WebApplicationBuilder builder)
{
    builder.Services.AddDbContext<AuthenticationDbContext>(options =>
    options.UseSqlite(builder.Configuration.GetConnectionString("dbuser")));

    builder.Services.AddDbContext<ProcessValueDbContext>(options =>
    options.UseSqlite(builder.Configuration.GetConnectionString("dbprocessvalue")), ServiceLifetime.Singleton);

    builder.Services.AddIdentity<ApplicationUser, ApplicationRole>()
        .AddEntityFrameworkStores<AuthenticationDbContext>()
        .AddDefaultTokenProviders();
}

void ConfigureAuthentication(WebApplicationBuilder builder)
{
    builder.Services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(options =>
    {
        var secret = builder.Configuration["JWT:Secret"];
        var audience = builder.Configuration["JWT:ValidAudience"];
        var validIssuer = builder.Configuration["JWT:ValidIssuer"];
        var keyInbytes = Encoding.UTF8.GetBytes(secret);

        options.SaveToken = true;
        options.RequireHttpsMetadata = false;
        options.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidAudience = audience,
            ValidIssuer = validIssuer,
            IssuerSigningKey = new SymmetricSecurityKey(keyInbytes)
        };
    }).AddNegotiate()
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
    {
        options.ExpireTimeSpan = TimeSpan.FromMinutes(15);
        options.SlidingExpiration = true;
    });
}

void ConfigureAuthorization(WebApplicationBuilder builder)
{
    builder.Services.AddAuthorization(options =>
    {
        options.AddPolicy(WellKnownPolicies.SystemManage, policy => policy.RequireClaim(WellKnownClaimTypes.Permission, WellKnownPermissionClaims.SystemManaging));
        options.AddPolicy(WellKnownPolicies.WriteProcessValue, policy => policy.RequireClaim(WellKnownClaimTypes.Permission, WellKnownPermissionClaims.WriteProcessValue));
        options.AddPolicy(WellKnownPolicies.ReadProcessValue, policy => policy.RequireClaim(WellKnownClaimTypes.Permission, WellKnownPermissionClaims.ReadProcessValue));
        options.AddPolicy(WellKnownPolicies.WriteHistoryValue, policy => policy.RequireClaim(WellKnownClaimTypes.Permission, WellKnownPermissionClaims.WriteHistoryValue));
        options.AddPolicy(WellKnownPolicies.ReadHistoryValue, policy => policy.RequireClaim(WellKnownClaimTypes.Permission, WellKnownPermissionClaims.ReadHistoryValue));
        options.AddPolicy(WellKnownPolicies.WriteSettings, policy => policy.RequireClaim(WellKnownClaimTypes.Permission, WellKnownPermissionClaims.WriteSettings));
        options.AddPolicy(WellKnownPolicies.ReadSettings, policy => policy.RequireClaim(WellKnownClaimTypes.Permission, WellKnownPermissionClaims.ReadSettings));
    });
}

static void RegisterSwagger(WebApplicationBuilder builder)
{
    //builder.Services.AddSwaggerGen(c =>
    //{
    //    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Web HMI", Version = "v1" });
    //    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    //    {
    //        Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n
    //                  Enter 'Bearer' [space] and then your token in the text input below.
    //                  \r\n\r\nExample: 'Bearer 12345abcdef'",
    //        Name = "Authorization",
    //        In = ParameterLocation.Header,
    //        Type = SecuritySchemeType.ApiKey,
    //        Scheme = "Bearer"
    //    });

    //    c.AddSecurityRequirement(new OpenApiSecurityRequirement()
    //{
    //    {
    //        new OpenApiSecurityScheme
    //        {
    //            Reference = new OpenApiReference
    //            {
    //                Type = ReferenceType.SecurityScheme,
    //                Id = "Bearer"
    //            },
    //            Scheme = "oauth2",
    //            Name = "Bearer",
    //            In = ParameterLocation.Header,
    //        },
    //        new List<string>()
    //    }
    //});
    //});
}

static void RegisterServices(WebApplicationBuilder builder)
{
    builder.Services.AddRazorPages();
    builder.Services.AddRadzenComponents();
    builder.Services.AddServerSideBlazor().AddHubOptions(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
        options.KeepAliveInterval = TimeSpan.FromSeconds(15);
        options.EnableDetailedErrors = true;
    }).AddCircuitOptions(options =>
    {
        options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromMinutes(5);
        options.DisconnectedCircuitMaxRetained = 100;
        options.DetailedErrors = true;
    });
    builder.Services.AddSingleton<IHistoryProcessValueCollector, HistoryProcessValueCollector>();
    builder.Services.AddSingleton<IHmiWatchdog, HmiWatchdog>();
    builder.Services.AddSingleton<IHistoryProcessValueService, HistoryProcessValueService>();
    builder.Services.AddSingleton<IAlarmService, AlarmService>();
    builder.Services.AddSingleton<IProjectService, ProjectService>();
    builder.Services.AddSingleton<IHistoryDataService, HistoryDataService>();
    builder.Services.AddSingleton<IProcessValuePollingService, ProcessValuePollingService>();
    builder.Services.AddSingleton<IAppDiagnosticService, AppDiagnosticService>();
    builder.Services.AddSingleton<IRegistrationService, RegistrationService>();
    builder.Services.AddSingleton<IOperationResultIssuesService, OperationResultIssuesService>();
    builder.Services.AddScoped<IPluginAuthenticationService, PluginAuthenticationService>();
    builder.Services.AddSingleton<IHmiLanguageService, HmiLanguageService>();
    builder.Services.AddSingleton<IDiagnoseService, DummyDiagnoseService>();
    builder.Services.AddSingleton<IProcessValueInfoService, ProcessValueInfoService>();
    builder.Services.AddSingleton<IProcessValueLoadService, ProcessValueLoadService>();
    //builder.Services.AddSingleton<IRegistrationService, RegistrationService>();
    builder.Services.AddScoped<IBrowserService, BrowserService>();
    builder.Services.AddScoped<IStepService, StepService>();
    builder.Services.AddScoped<ICachingService, CachingService>();
    builder.Services.AddScoped<IProcessDataDbTestSetupService, ProcessDataDbTestSetupService>();
    builder.Services.AddScoped<ISelmoAuthenticationService, SelmoAuthenticationService>();
    builder.Services.AddScoped<DialogService>();
    builder.Services.AddScoped<IDialogService, RadzenDialogServiceImplementation>();
    builder.Services.AddScoped<IFileDownloadService, FileDownloadService>();
    builder.Services.AddScoped<INotificationService, HmiNotificationService>();
    builder.Services.AddScoped<INotificationImplementation, RadzenNotificationImplementation>();
    builder.Services.AddScoped<IProgressService, ProgressService>();
    // builder.Services.AddSingleton<IPluginAuthenticationService, PluginAuthenticationService>();
    //builder.Services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomClaimsPrincipalFactory>();
    builder.Services.AddBlazorBootstrap();
    builder.Services.AddRadzenComponents();
    builder.Services.AddTransient<IClaimsTransformation, CustomClaimsTransformation>();
    builder.Services.AddScoped<IProjectInformationService, ProjectInformationService>();
    builder.Services.AddSingleton<IAuditTrailService, AuditTrailService>();
    builder.Services.AddScoped<IAuthenticationStateService, AuthenticationStateService>();
    builder.Services.AddTransient<IViewportResizeService, ViewportResizeService>();
    builder.Services.AddScoped<CircuitHandler, CustomCircuitHandler>();
    builder.Services.AddScoped<IConnectionService, ConnectionService>();
}

void ConfigureDefaultPolicy(WebApplicationBuilder builder)
{
    //https://learn.microsoft.com/de-de/aspnet/core/security/cors?view=aspnetcore-9.0
    builder.Services.AddCors(options =>
    {
        options.AddPolicy(name: PolicyAllowSpecificOrigins,
            policy =>
            {
                policy.WithOrigins("https://www.w3schools.com");
            });
    });
}

async Task InitSetupData(WebApplication host, ILogger<Program> log)
{
    using var scope = host.Services.CreateScope();
    var services = scope.ServiceProvider;

    try
    {
        var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
        var roleManager = services.GetRequiredService<RoleManager<ApplicationRole>>();
        var claimsTransformation = services.GetRequiredService<IClaimsTransformation>();

        EnsureDbFolderExists();
        var contextGeneral = services.GetRequiredService<AuthenticationDbContext>();
        contextGeneral.Database.EnsureCreated();
        var contextProcessValue = services.GetRequiredService<ProcessValueDbContext>();
        contextProcessValue.Database.EnsureCreated();
        var projectService = services.GetRequiredService<IProjectService>();
        await DefaultDataInitializer.SeedData(userManager, roleManager, projectService.GetUserList());
    }
    catch (Exception ex)
    {
        log.LogError($"Error in {nameof(InitSetupData)}(); => {ex.Message}");
        throw;
    }
}

void EnsureDbFolderExists()
{
    var dbPath = Path.Combine("./Db/SqLite");
    if (!Directory.Exists(dbPath))
    {
        Directory.CreateDirectory(dbPath);
    }
}

void InitServiceData(WebApplication host, ILogger<Program> log)
{
    using var scope = host.Services.CreateScope();
    var services = scope.ServiceProvider;

    try
    {
        var projectService = services.GetRequiredService<IProjectService>();

        var processValuePollingService = services.GetRequiredService<IProcessValuePollingService>();

        var hmiSettings = projectService.GetHmiSettings();
        var settings = CreateConnectionSettings(hmiSettings);

        log.LogInformation($"**Registered DATA connection**: {GetConnectionInfo(hmiSettings)}");

        var processValuesDefinition = projectService.GetProcessValueDefinitions();

        processValuePollingService.Init(settings);
        //.IsConncetionActive();
        var processValueInfoService = services.GetRequiredService<IProcessValueInfoService>();

        var mapped = HmiProcessValueHelper.GenerateProcessValues(processValuesDefinition, hmiSettings);
        processValueInfoService.Init(mapped);

        /* var regService = services.GetRequiredService<IRegistrationService>();
         regService.Init();*/

        var hmiWatchdog = services.GetRequiredService<IHmiWatchdog>();
        hmiWatchdog.Init();

        var operationResultIssuesService = services.GetRequiredService<IOperationResultIssuesService>();
        operationResultIssuesService.Init(new List<Severity> { Severity.Error, Severity.Fatal });

        List<HistoryValueGroupSettings> collectionSettings = new();
        var historyProcessValueCollector = services.GetRequiredService<IHistoryProcessValueCollector>();
        historyProcessValueCollector.Start(collectionSettings);

        var hmiLanguages = HmiLanguageFactory.CreateFromLcid(projectService.GetLanguageLcidCodes());
        var defaultHmiLanguage = hmiLanguages.FirstOrDefault(l => l.Lcid.Equals(projectService.GetDefaultLanguageLcidCode()));

        var hmiLanguageService = services.GetRequiredService<IHmiLanguageService>();
        hmiLanguageService.Init(hmiLanguages, defaultHmiLanguage ?? WellKnownLanguages.FallbackLanguage);
        hmiLanguageService.SetStartup();

        var pluginAuthenticationService = services.GetRequiredService<IPluginAuthenticationService>();
    }
    catch (Exception ex)
    {
        log.LogError($"Error in {nameof(InitServiceData)}(); => {ex.Message}");
        throw;
    }
}

IConnectionSettings CreateConnectionSettings(HmiSettings settings)
{
    var settingsBuilder = ConnectionSettingsBuilder.New(new(Guid.NewGuid()), settings.ConnectionType);
    settingsBuilder.SetUpdateRate(settings.UpdateRate);

    if (settings.ConnectionType == ConnectionType.SimulatedFromFile)
    {
        return TestValuesForDemo.GetSettingsForSimulation();
    }

    if (settings.ConnectionType == ConnectionType.Ads)
    {
        settingsBuilder.SetPort(settings.AdsPort);
        settingsBuilder.SetAmsNetId(settings.AmsNetId);
        return settingsBuilder.Build();
    }

    if (settings.ConnectionType == ConnectionType.OpcUa)
    {
        settingsBuilder.SetOpcUaServerAddress(settings.OpcUaServerAddress);
        settingsBuilder.SetOpcUaSecurityPolicy(settings.OpcUaSecurityPolicy);
        settingsBuilder.SetOpcUaSecurityMode(settings.OpcUaSecurityMode);
        settingsBuilder.SetOpcUaUsername(settings.OpcUaUsername);
        settingsBuilder.SetOpcUaPassword(settings.OpcUaPassword);
        settingsBuilder.SetBasePath(settings.BasePath);
        settingsBuilder.SetBaseDivider(settings.BaseDivider);
        settingsBuilder.SetBaseNamespaceIndex(settings.BaseNamespaceIndex);
        return settingsBuilder.Build();
    }

    //TODO ADD OPC UA
    throw new ArgumentException($"Not Supported {nameof(settings.ConnectionType)}: {settings.ConnectionType}");
}

static string GetConnectionInfo(HmiSettings settings)
{
    StringBuilder builder = new StringBuilder();
    builder.AppendLine($"ConnectionType: {settings.ConnectionType}");

    if (settings.ConnectionType == ConnectionType.Ads)
    {
        builder.AppendLine($"AdsPort: {settings.AdsPort}");
        builder.AppendLine($"AmsNetId: {settings.AmsNetId}");
    }

    if (settings.ConnectionType == ConnectionType.OpcUa)
    {
        builder.AppendLine($"OpcUaServerAddress: {settings.OpcUaServerAddress}");
        builder.AppendLine($"OpcUaSecurityPolicy: {settings.OpcUaSecurityPolicy}");
        builder.AppendLine($"OpcUaSecurityMode: {settings.OpcUaSecurityMode}");
        builder.AppendLine($"OpcUaUsername: {settings.OpcUaUsername}");
        builder.AppendLine($"OpcUaPassword: {settings.OpcUaPassword}");
        builder.AppendLine($"BasePath: {settings.BasePath}");
        builder.AppendLine($"BaseDivider: {settings.BaseDivider}");
        builder.AppendLine($"BaseNamespaceIndex: {settings.BaseNamespaceIndex}");
    }
    return builder.ToString();
}

static void ConfigureLogging(WebApplicationBuilder builder)
{
    //using var scope = host.Services.CreateScope();
    //var services = scope.ServiceProvider;

    //var projectService = services.GetRequiredService<IProjectService>();
    //var hmiSettings = projectService.GetHmiSettings();

    //!!!!! TODO eric add logfilesettings to other hmi settings

    var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
    var seriLogFileLoggerFilePath = Path.Combine(path, "SelmoHmi", "Logging", "log.txt");

    builder.Host.UseSerilog((ctx, lc) => lc
        .MinimumLevel.Information()
        .WriteTo.Console(Serilog.Events.LogEventLevel.Information)
        .WriteTo.File(seriLogFileLoggerFilePath,
                        restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Verbose,
                        rollingInterval: RollingInterval.Hour,
                        retainedFileCountLimit: 15,
                        encoding: System.Text.Encoding.UTF8));
}