using System.Data;
using System.Text.Json;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
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 sealed record PlatformAdminBootstrapOptions(
    string Email,
    string TemporaryPassword,
    string? DisplayName = null);

public sealed record PlatformAdminBootstrapResult(Guid UserId, Guid RoleId, string Email);

public sealed class PlatformAdminBootstrapper(
    TikuDbContext dbContext,
    UserManager<User> userManager)
{
    public const string SuperAdminRoleCode = "platform_super_admin";

    public async Task<PlatformAdminBootstrapResult> BootstrapAsync(
        PlatformAdminBootstrapOptions options,
        CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(options);
        var email = options.Email.Trim();
        if (email.Length == 0)
            throw new ArgumentException("Platform administrator email is required.", nameof(options));

        if (string.IsNullOrWhiteSpace(options.TemporaryPassword))
            throw new ArgumentException("Platform administrator temporary password is required.", nameof(options));

        IDbContextTransaction? transaction = null;
        if (dbContext.Database.IsRelational())
            transaction =
                await dbContext.Database.BeginTransactionAsync(IsolationLevel.Serializable, cancellationToken);

        await using (transaction)
        {
            var existingAdministrator = await (
                    from binding in dbContext.PlatformBackendUserRoles.AsNoTracking()
                    join boundRole in dbContext.PlatformBackendRoles.AsNoTracking() on binding.RoleId equals boundRole
                        .Id
                    join boundUser in dbContext.Users.AsNoTracking() on binding.UserId equals boundUser.Id
                    where boundRole.Status == BackendRoleStatus.Active && boundUser.Status == UserStatus.Active
                    select boundUser.Id)
                .AnyAsync(cancellationToken);
            if (existingAdministrator)
                throw new PlatformAdminBootstrapException(
                    "A platform administrator already exists. Bootstrap is a one-time operation.",
                    "platform_admin_already_exists");

            var normalizedEmail = userManager.NormalizeEmail(email);
            if (await dbContext.Users.AsNoTracking().AnyAsync(
                    user => user.NormalizedEmail == normalizedEmail || user.NormalizedUserName == normalizedEmail,
                    cancellationToken))
                throw new PlatformAdminBootstrapException(
                    "The bootstrap email is already assigned to a user.",
                    "bootstrap_user_already_exists");

            var user = new User
            {
                Email = email,
                UserName = email,
                Name = string.IsNullOrWhiteSpace(options.DisplayName)
                    ? "Platform Administrator"
                    : options.DisplayName.Trim(),
                EmailConfirmed = true,
                Status = UserStatus.Active,
                ForcePasswordChange = true
            };
            var createResult = await userManager.CreateAsync(user, options.TemporaryPassword);
            if (!createResult.Succeeded)
            {
                var errors = string.Join(", ",
                    createResult.Errors.Select(error => $"{error.Code}: {error.Description}"));
                throw new PlatformAdminBootstrapException(
                    $"Platform administrator could not be created: {errors}",
                    "bootstrap_user_invalid");
            }

            var role = new PlatformBackendRole
            {
                Code = SuperAdminRoleCode,
                Name = "Platform Super Administrator",
                Description = "Built-in role with all platform permissions. Created by the one-time bootstrap command.",
                Status = BackendRoleStatus.Active,
                IsSystem = true
            };
            dbContext.PlatformBackendRoles.Add(role);

            var platformPermissionCodes = BackendPermissions.Platform.ToArray();
            var platformModuleCodes = platformPermissionCodes
                .Select(PermissionModuleCatalog.ResolvePermissionModuleCode)
                .Distinct(StringComparer.Ordinal)
                .ToArray();
            var existingModuleCodes = await dbContext.PermissionModules
                .Where(module => platformModuleCodes.Contains(module.Code))
                .Select(module => module.Code)
                .ToArrayAsync(cancellationToken);
            dbContext.PermissionModules.AddRange(platformModuleCodes
                .Except(existingModuleCodes, StringComparer.Ordinal)
                .Select(code => new PermissionModule
                {
                    Code = code,
                    Name = code,
                    Area = BackendPermissionArea.Platform
                }));
            var existingPermissionCodes = await dbContext.BackendPermissions
                .Where(permission => platformPermissionCodes.Contains(permission.Code))
                .Select(permission => permission.Code)
                .ToHashSetAsync(StringComparer.Ordinal, cancellationToken);
            foreach (var permissionCode in
                     platformPermissionCodes.Where(code => !existingPermissionCodes.Contains(code)))
                dbContext.BackendPermissions.Add(new BackendPermission
                {
                    Code = permissionCode,
                    Name = permissionCode,
                    Area = BackendPermissionArea.Platform,
                    PermissionModuleCode = PermissionModuleCatalog.ResolvePermissionModuleCode(permissionCode),
                    Description = "Built-in platform permission.",
                    IsSystem = true
                });

            dbContext.PlatformBackendRolePermissions.AddRange(
                platformPermissionCodes.Select(permissionCode => new PlatformBackendRolePermission
                {
                    RoleId = role.Id,
                    PermissionCode = permissionCode
                }));
            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 = SuperAdminRoleCode,
                    ForcePasswordChange = true,
                    LoginMethod = "account_password"
                })
            });

            await dbContext.SaveChangesAsync(cancellationToken);
            if (transaction is not null) await transaction.CommitAsync(cancellationToken);

            return new PlatformAdminBootstrapResult(user.Id, role.Id, email);
        }
    }
}

public sealed class PlatformAdminBootstrapException(string message, string code) : InvalidOperationException(message)
{
    public string Code { get; } = code;
}