using Baya.Application.Contracts.Identity;
using Baya.Application.Models.Identity;
using Baya.Domain.Entities.User;
using Baya.Infrastructure.Identity.Identity.Manager;
using Baya.Infrastructure.Identity.Identity.PermissionManager;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;

namespace Baya.Infrastructure.Identity.Identity.SeedDatabaseService;

public interface ISeedDataBase
{
    Task Seed();
}

public class SeedDataBase : ISeedDataBase
{
    private readonly AppUserManager _userManager;
    private readonly AppRoleManager _roleManager;
    private readonly IConfiguration _configuration;
    private readonly IRoleManagerService _roleManagerService;

    // The admin-console permission matrix owned by each non-admin sub-role — mirrors
    // client/src/hooks/capabilities.ts (useAdminCapabilities), the reviewed source of truth for who
    // owns which console. Area is always empty: no controller uses [Area] today.
    private static readonly IReadOnlyDictionary<string, string[]> AdminSubRolePermissions =
        new Dictionary<string, string[]>
        {
            [RoleNames.Finance] = ["AdminRefunds", "AdminPayouts", "PlatformConfig"],
            [RoleNames.Support] = ["AdminVerifications", "AdminVerificationStepTypes", "SupportAlerts", "AdminTickets"],
            [RoleNames.Moderation] = ["AdminReviews", "Reviews"],
        };

    public SeedDataBase(AppUserManager userManager, AppRoleManager roleManager, IConfiguration configuration, IRoleManagerService roleManagerService)
    {
        _userManager = userManager;
        _roleManager = roleManager;
        _configuration = configuration;
        _roleManagerService = roleManagerService;
    }

    public async Task Seed()
    {
        // The full role vocabulary: public actor roles (customer/nurse — self-selectable) and the
        // admin sub-roles (internally provisioned only, never self-assignable).
        foreach (var roleName in RoleNames.All)
        {
            if (!_roleManager.Roles.AsNoTracking().Any(r => r.Name.Equals(roleName)))
            {
                await _roleManager.CreateAsync(new Role
                {
                    Name = roleName,
                });
            }
        }

        await SeedAdminSubRolePermissionsAsync();
        await SeedBootstrapAdminAsync();
    }

    // finance/support/moderation are sibling roles, not implied by super_admin — each needs its own
    // DynamicPermission claims scoped to the consoles it owns. Skips roles already at the target claim
    // set so a normal restart doesn't churn every affected user's security stamp.
    private async Task SeedAdminSubRolePermissionsAsync()
    {
        foreach (var (roleName, controllers) in AdminSubRolePermissions)
        {
            var role = await _roleManager.FindByNameAsync(roleName);
            if (role == null)
                continue;

            var targetPermissions = controllers.Select(controller => $":{controller}:").ToList();

            var existingPermissions = (await _roleManager.GetClaimsAsync(role))
                .Where(c => c.Type == ConstantPolicies.DynamicPermission)
                .Select(c => c.Value)
                .ToHashSet(StringComparer.OrdinalIgnoreCase);

            if (existingPermissions.SetEquals(targetPermissions))
                continue;

            await _roleManagerService.ChangeRolePermissionsAsync(new EditRolePermissionsDto
            {
                RoleId = role.Id,
                Permissions = targetPermissions
            });
        }
    }

    // The bootstrap admin is config-driven, never a committed credential: it is created only when both
    // Seed:AdminUsername and Seed:AdminPassword are supplied (via the environment-specific appsettings file, environment
    // variables in a deployment). With neither configured — the default for Testing and any fresh boot —
    // no admin account is created, so no well-known password ever lands in a real database. Day-to-day
    // admins reach the backoffice through the phone-OTP demo seeds (Development) or are provisioned
    // out-of-band; this account is a break-glass bootstrap only.
    private async Task SeedBootstrapAdminAsync()
    {
        var username = _configuration["Seed:AdminUsername"];
        var password = _configuration["Seed:AdminPassword"];

        if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
            return;

        if (_userManager.Users.AsNoTracking().Any(u => u.UserName.Equals(username)))
            return;

        var user = new User
        {
            UserName = username,
            Email = _configuration["Seed:AdminEmail"] ?? "admin@balinyaar.local",
            PhoneNumberConfirmed = true,
            IsActive = true
        };

        await _userManager.CreateAsync(user, password);
        await _userManager.AddToRoleAsync(user, "admin");
    }
}