using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Roles;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options; // For configuration
using System.Linq.Expressions;

namespace Marco.Pms.Services.Service
{
    // A configuration class to hold settings from appsettings.json
    // This avoids hardcoding sensitive information.
    public class SuperAdminSettings
    {
        public const string CONFIG_SECTION_NAME = "SuperAdminAccount";
        public string Email { get; set; } = "admin@marcoaiot.com";
        public string Password { get; set; } = "User@123";
        public string TenantId { get; set; } = "b3466e83-7e11-464c-b93a-daf047838b26";
    }

    public class StartupUserSeeder : IHostedService
    {
        private readonly IServiceProvider _serviceProvider;
        private readonly ILogger<StartupUserSeeder> _logger;

        // Constants to avoid "magic strings"
        private const string AdminJobRoleName = "Admin";
        private const string SuperUserRoleName = "Super User";

        public StartupUserSeeder(IServiceProvider serviceProvider, ILogger<StartupUserSeeder> logger)
        {
            _serviceProvider = serviceProvider;
            _logger = logger;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Starting database seeding process...");

            using var scope = _serviceProvider.CreateScope();
            var serviceProvider = scope.ServiceProvider;

            // Get services from the scoped provider
            var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();
            var dbContext = serviceProvider.GetRequiredService<ApplicationDbContext>();
            var adminSettings = serviceProvider.GetRequiredService<IOptions<SuperAdminSettings>>().Value;
            var tenantId = Guid.Parse(adminSettings.TenantId);

            // Use a database transaction to ensure all operations succeed or none do.
            await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);

            try
            {
                // 1. Seed the Application User (Super Admin)
                var user = await SeedSuperAdminUserAsync(userManager, adminSettings, tenantId);

                // 2. Seed the Job Role
                var jobRole = await GetOrCreateAsync(
                    dbContext.JobRoles,
                    j => j.Name == AdminJobRoleName && j.TenantId == tenantId,
                    () => new JobRole
                    {
                        Name = AdminJobRoleName,
                        Description = "Administrator with full system access.",
                        TenantId = tenantId
                    });

                // 3. Seed the Application Role
                var appRole = await GetOrCreateAsync(
                    dbContext.ApplicationRoles,
                    a => a.Role == SuperUserRoleName && a.TenantId == tenantId,
                    () => new ApplicationRole
                    {
                        Role = SuperUserRoleName,
                        Description = "System role with all permissions.",
                        IsSystem = true,
                        TenantId = tenantId
                    });

                // 4. Seed the Employee record linked to the user and job role
                var employee = await GetOrCreateAsync(
                    dbContext.Employees,
                    e => e.Email == adminSettings.Email && e.TenantId == tenantId,
                    () => new Employee
                    {
                        ApplicationUserId = user.Id,
                        FirstName = "Admin",
                        LastName = "User",
                        Email = adminSettings.Email,
                        TenantId = tenantId,
                        PhoneNumber = "9876543210",
                        JobRoleId = jobRole.Id,
                        IsSystem = true,
                        JoiningDate = DateTime.UtcNow,
                        BirthDate = new DateTime(1970, 1, 1)
                        // Set other non-nullable fields to sensible defaults
                    });

                // 5. Seed the Employee-Role Mapping
                await GetOrCreateAsync(
                    dbContext.EmployeeRoleMappings,
                    erm => erm.EmployeeId == employee.Id && erm.RoleId == appRole.Id,
                    () => new EmployeeRoleMapping
                    {
                        EmployeeId = employee.Id,
                        RoleId = appRole.Id,
                        TenantId = tenantId,
                        IsEnabled = true
                    });

                // 6. Seed Role Permissions (Efficiently)
                await SeedRolePermissionsAsync(dbContext, appRole.Id);

                // All entities are now tracked by the DbContext.
                // A single SaveChanges call is more efficient.
                await dbContext.SaveChangesAsync(cancellationToken);

                // If all operations were successful, commit the transaction.
                await transaction.CommitAsync(cancellationToken);
                _logger.LogInformation("Database seeding process completed successfully.");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An error occurred during database seeding. Rolling back changes.");
                await transaction.RollbackAsync(cancellationToken);
                // Optionally re-throw or handle the exception as needed
                throw;
            }
        }

        private async Task<ApplicationUser> SeedSuperAdminUserAsync(UserManager<ApplicationUser> userManager, SuperAdminSettings settings, Guid tenantId)
        {
            _logger.LogInformation("Seeding Super Admin user: {Email}", settings.Email);
            var user = await userManager.FindByEmailAsync(settings.Email);
            if (user == null)
            {
                user = new ApplicationUser
                {
                    UserName = settings.Email,
                    Email = settings.Email,
                    EmailConfirmed = true,
                    IsRootUser = true
                };
                var result = await userManager.CreateAsync(user, settings.Password);

                if (!result.Succeeded)
                {
                    // If user creation fails, it's a critical error.
                    var errors = string.Join(", ", result.Errors.Select(e => e.Description));
                    _logger.LogError("Failed to create super admin user. Errors: {Errors}", errors);
                    throw new InvalidOperationException($"Failed to create super admin user: {errors}");
                }
                _logger.LogInformation("Super Admin user created successfully.");
            }
            else
            {
                _logger.LogInformation("Super Admin user already exists.");
            }
            return user;
        }

        private async Task SeedRolePermissionsAsync(ApplicationDbContext dbContext, Guid superUserRoleId)
        {
            _logger.LogInformation("Seeding permissions for Super User role (ID: {RoleId})", superUserRoleId);

            var allPermissionIds = await dbContext.FeaturePermissions
                                                  .Select(p => p.Id)
                                                  .ToListAsync();

            var permissionIdsFromDb = await dbContext.RolePermissionMappings
                                         .Where(pm => pm.ApplicationRoleId == superUserRoleId)
                                         .Select(pm => pm.FeaturePermissionId)
                                         .ToListAsync(); // 1. Fetch data from DB into a List

            var existingPermissionIds = new HashSet<Guid>(permissionIdsFromDb); // 2. Convert the List to a HashSet in memory

            var missingPermissionIds = allPermissionIds.Except(existingPermissionIds).ToList();

            if (missingPermissionIds.Any())
            {
                var newMappings = missingPermissionIds.Select(permissionId => new RolePermissionMappings
                {
                    ApplicationRoleId = superUserRoleId,
                    FeaturePermissionId = permissionId
                });

                await dbContext.RolePermissionMappings.AddRangeAsync(newMappings);
                _logger.LogInformation("Added {Count} new permission mappings to the Super User role.", missingPermissionIds.Count);
            }
            else
            {
                _logger.LogInformation("Super User role already has all available permissions.");
            }
        }

        /// <summary>
        /// A generic helper to find an entity by a predicate or create, add, and return it if not found.
        /// This promotes code reuse and makes the main logic cleaner.
        /// </summary>
        private async Task<T> GetOrCreateAsync<T>(DbSet<T> dbSet, Expression<Func<T, bool>> predicate, Func<T> factory) where T : class
        {
            var entity = await dbSet.FirstOrDefaultAsync(predicate);
            if (entity == null)
            {
                entity = factory();
                await dbSet.AddAsync(entity);
                _logger.LogInformation("Creating new entity of type {EntityType}.", typeof(T).Name);
            }
            return entity;
        }

        public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
    }
}