using System.Security.Cryptography;
using System.Text.Json;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Npgsql;
using Tiku.Application.Security;
using Tiku.Domain.Identity;
using Tiku.Domain.Operations;
using Tiku.Domain.Platform;
using Tiku.Infrastructure.Persistence;

namespace Tiku.Infrastructure.Bootstrap;

public static class DevelopmentPlatformAdminSeeder
{
    public const string Email = "admin@tiku.local";
    public const string RoleCode = PlatformAdminBootstrapper.SuperAdminRoleCode;

    public static void Configure(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSeeding((context, _) => Seed((TikuDbContext)context))
            .UseAsyncSeeding((context, _, cancellationToken) =>
                SeedAsync((TikuDbContext)context, cancellationToken));
    }

    public static bool Seed(TikuDbContext dbContext)
    {
        ReloadPostgresTypes(dbContext);

        if (dbContext.PlatformBackendUserRoles.Any())
        {
            return false;
        }

        var temporaryPassword = GenerateTemporaryPassword();
        EnsureEmailIsAvailable(dbContext.Users.Any(user =>
            user.NormalizedEmail == Email.ToUpperInvariant() ||
            user.NormalizedUserName == Email.ToUpperInvariant()));
        var permissionCodes = BackendPermissions.Platform.ToArray();
        var existingPermissionCodes = dbContext.BackendPermissions
            .Where(permission => permissionCodes.Contains(permission.Code))
            .Select(permission => permission.Code)
            .ToHashSet(StringComparer.Ordinal);
        AddSeedGraph(dbContext, temporaryPassword, permissionCodes, existingPermissionCodes);
        dbContext.SaveChanges();
        WriteFirstLoginInstructions(temporaryPassword);
        return true;
    }

    public static async Task<bool> SeedAsync(
        TikuDbContext dbContext,
        CancellationToken cancellationToken = default)
    {
        await ReloadPostgresTypesAsync(dbContext, cancellationToken);

        if (await dbContext.PlatformBackendUserRoles.AnyAsync(cancellationToken))
        {
            return false;
        }

        var temporaryPassword = GenerateTemporaryPassword();
        var normalizedEmail = Email.ToUpperInvariant();
        EnsureEmailIsAvailable(await dbContext.Users.AnyAsync(user =>
            user.NormalizedEmail == normalizedEmail ||
            user.NormalizedUserName == normalizedEmail, cancellationToken));
        var permissionCodes = BackendPermissions.Platform.ToArray();
        var existingPermissionCodes = (await dbContext.BackendPermissions
                .Where(permission => permissionCodes.Contains(permission.Code))
                .Select(permission => permission.Code)
                .ToArrayAsync(cancellationToken))
            .ToHashSet(StringComparer.Ordinal);
        AddSeedGraph(dbContext, temporaryPassword, permissionCodes, existingPermissionCodes);
        await dbContext.SaveChangesAsync(cancellationToken);
        WriteFirstLoginInstructions(temporaryPassword);
        return true;
    }

    private static void AddSeedGraph(
        TikuDbContext dbContext,
        string temporaryPassword,
        IReadOnlyCollection<string> permissionCodes,
        IReadOnlySet<string> existingPermissionCodes)
    {
        var normalizedEmail = Email.ToUpperInvariant();
        var user = new User
        {
            Email = Email,
            NormalizedEmail = normalizedEmail,
            UserName = Email,
            NormalizedUserName = normalizedEmail,
            Name = "Local Platform Administrator",
            EmailConfirmed = true,
            Status = UserStatus.Active,
            ForcePasswordChange = true
        };
        var passwordHasher = new PasswordHasher<User>(Options.Create(new PasswordHasherOptions
        {
            IterationCount = 210_000
        }));
        user.PasswordHash = passwordHasher.HashPassword(user, temporaryPassword);

        var role = new PlatformBackendRole
        {
            Code = RoleCode,
            Name = "Platform Super Administrator",
            Description = "Built-in Development administrator created by EF Core data seeding.",
            Status = BackendRoleStatus.Active,
            IsSystem = true
        };
        dbContext.Users.Add(user);
        dbContext.PlatformBackendRoles.Add(role);
        var platformModuleCodes = permissionCodes
            .Select(PermissionModuleCatalog.ResolvePermissionModuleCode)
            .Distinct(StringComparer.Ordinal)
            .ToArray();
        var existingModuleCodes = dbContext.PermissionModules
            .Where(module => platformModuleCodes.Contains(module.Code))
            .Select(module => module.Code)
            .ToArray();
        dbContext.PermissionModules.AddRange(platformModuleCodes
            .Except(existingModuleCodes, StringComparer.Ordinal)
            .Select(code => new PermissionModule
            {
                Code = code,
                Name = code,
                Area = BackendPermissionArea.Platform
            }));
        dbContext.BackendPermissions.AddRange(
            permissionCodes
                .Where(code => !existingPermissionCodes.Contains(code))
                .Select(code => new BackendPermission
                {
                    Code = code,
                    Name = code,
                    Area = BackendPermissionArea.Platform,
                    PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(code),
                    Description = "Built-in platform permission.",
                    IsSystem = true
                }));
        dbContext.PlatformBackendRolePermissions.AddRange(
            permissionCodes.Select(code => new PlatformBackendRolePermission
            {
                RoleId = role.Id,
                PermissionCode = code
            }));
        dbContext.PlatformBackendUserRoles.Add(new PlatformBackendUserRole
        {
            UserId = user.Id,
            RoleId = role.Id
        });
        dbContext.AuditLogs.Add(new AuditLog
        {
            ActorUserId = user.Id,
            Action = "platform.bootstrap_admin.created",
            TargetType = "users",
            TargetId = user.Id.ToString(),
            Details = JsonSerializer.SerializeToElement(new
            {
                user.Email,
                RoleCode,
                ForcePasswordChange = true,
                Source = "ef_core_use_seeding"
            })
        });
    }

    private static string GenerateTemporaryPassword() =>
        $"Tiku!{Convert.ToHexString(RandomNumberGenerator.GetBytes(16))}9a";

    private static void ReloadPostgresTypes(TikuDbContext dbContext)
    {
        if (dbContext.Database.IsNpgsql())
        {
            ((NpgsqlConnection)dbContext.Database.GetDbConnection()).ReloadTypes();
        }
    }

    private static async Task ReloadPostgresTypesAsync(
        TikuDbContext dbContext,
        CancellationToken cancellationToken)
    {
        if (dbContext.Database.IsNpgsql())
        {
            await ((NpgsqlConnection)dbContext.Database.GetDbConnection())
                .ReloadTypesAsync(cancellationToken);
        }
    }

    private static void EnsureEmailIsAvailable(bool isAssigned)
    {
        if (isAssigned)
        {
            throw new InvalidOperationException(
                $"Cannot create the Development platform administrator because '{Email}' is already assigned.");
        }
    }

    private static void WriteFirstLoginInstructions(string temporaryPassword)
    {
        Console.WriteLine("Development platform administrator created by EF Core data seeding.");
        Console.WriteLine($"  Account: {Email}");
        Console.WriteLine($"  Temporary password: {temporaryPassword}");
        Console.WriteLine("  Change the temporary password at first sign-in. This password is shown only once.");
    }
}
