using DotNetEnv.Configuration;
using Wishlist.Components.Account;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Wishlist.Components;
using Wishlist.Data;
using Wishlist.Data.Entities;
using Wishlist.Data.Services;
using Wishlist.Data.Services.Interfaces;

namespace Wishlist;

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Adds .env to envs
        builder.Configuration.AddDotNetEnv(".env");

        // Add services to the container.
        builder.Services.AddRazorComponents()
            .AddInteractiveServerComponents();
        
        builder.Services.AddCascadingAuthenticationState();
        builder.Services.AddScoped<IdentityUserAccessor>();
        builder.Services.AddScoped<IdentityRedirectManager>();
        builder.Services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
        builder.Services.AddScoped<IWishlistService, WishlistService>();
        builder.Services.AddScoped<IWishService, WishService>();

        builder.Services.AddAuthentication(options =>
            {
                options.DefaultScheme = IdentityConstants.ApplicationScheme;
                options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
            })
            .AddIdentityCookies();
        var envDb = Environment.GetEnvironmentVariable("DB_CONNECTION");
        // It will try and use a connection string from env, then default else it will fatal with error. 
        var connectionString = 
            envDb ??
            builder.Configuration.GetConnectionString("DefaultConnection") ??
            throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
        
        builder.Services.AddDbContext<AppDataContext>(options =>
            options.UseNpgsql(connectionString));
        builder.Services.AddDatabaseDeveloperPageExceptionFilter();
        builder.Services.AddAuthorization();
        builder.Services.AddIdentityCore<User>(options => options.SignIn.RequireConfirmedAccount = true)
            .AddRoles<IdentityRole>()
            .AddEntityFrameworkStores<AppDataContext>()
            .AddSignInManager()
            .AddDefaultTokenProviders();

        builder.Services.AddSingleton<IEmailSender<User>, IdentityNoOpEmailSender>();
        
        var app = builder.Build();

        await CreateDataDefaults(app);

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

        app.MapStaticAssets();
        app.MapRazorComponents<App>()
            .AddInteractiveServerRenderMode();

        // Add additional endpoints required by the Identity /Account Razor components.
        app.MapAdditionalIdentityEndpoints();

        app.Run();
    }

    private static async Task CreateDataDefaults(WebApplication app)
    {
        try
        {
            using (var scope = app.Services.CreateScope())
            {
                // Create roles, if they don't exist
                var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
                foreach (var role in Constants.Roles)
                {
                    if (!await roleManager.RoleExistsAsync(role))
                    {
                        await roleManager.CreateAsync(new IdentityRole(role));
                    }
                }
                // Create test admin user
                if (app.Environment.IsDevelopment())
                {
                    var userManager = scope.ServiceProvider.GetRequiredService<UserManager<User>>();
                    var adminUser = await userManager.FindByNameAsync("admin");
                    if (adminUser == null)
                    {
                        var result = await userManager.CreateAsync(new User()
                        {
                            UserName =  "admin",
                        }, "Admin@123");
                        if (result.Succeeded == false)
                        {
                            throw new Exception($"An error occurred while creating the admin user, {string.Join('\n',result.Errors)}");
                        }
                        
                        adminUser = await userManager.FindByNameAsync("admin");
                        if (adminUser == null)
                        {
                            throw new Exception("Admin user not found, after being created.");
                        }

                        await userManager.AddToRolesAsync(adminUser, Constants.Roles);
                        var code = await userManager.GenerateEmailConfirmationTokenAsync(adminUser);
                        await userManager.ConfirmEmailAsync(adminUser, code);
                    }
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            throw;
        }
    }
}