using CashLineERP.Application.Finance;
using CashLineERP.Application.Identity;
using CashLineERP.Application.Integration;
using CashLineERP.Application.Inventory;
using CashLineERP.Application.IslamicFinance.ContractTemplates;
using CashLineERP.Application.IslamicFinance.Disbursements;
using CashLineERP.Application.IslamicFinance.Facilities;
using CashLineERP.Application.IslamicFinance.Murabaha;
using CashLineERP.Application.IslamicFinance.PartnerLenders;
using CashLineERP.Application.IslamicFinance.ProductProfiles;
using CashLineERP.Application.IslamicFinance.Requests;
using CashLineERP.Application.IslamicFinance.Settlements;
using CashLineERP.Application.IslamicFinance.Shariah;
using CashLineERP.Application.Procurement;
using CashLineERP.Application.Sales;
using CashLineERP.Contracts.Finance;
using CashLineERP.Contracts.Inventory;
using CashLineERP.Contracts.IslamicFinance;
using CashLineERP.Contracts.Procurement;
using CashLineERP.Contracts.Sales;
using CashLineERP.Domain.Identity;
using CashLineERP.Infrastructure.Persistence;
using CashLineERP.SharedKernel.Identity;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Api.Infrastructure;

internal static class DemoUatBootstrapper
{
    private const string InternalMakerLogin = "DEMO_OPS_MKR";
    private const string ProcurementMakerLogin = "DEMO_PROC_MKR";
    private const string InventoryControllerLogin = "DEMO_INV_CTRL";
    private const string SalesOperationsLogin = "DEMO_SALES_OPS";
    private const string FinanceMakerLogin = "DEMO_FIN_MKR";
    private const string InternalCheckerLogin = "DEMO_FIN_CHK";
    private const string IslamicOperationsLogin = "DEMO_IF_OPS";
    private const string InternalPassword = "Demo@2026!";
    private const string SupplierLogin = "DEMO_SUPPLIER";
    private const string CustomerLogin = "DEMO_CUSTOMER";
    private const string ExternalPassword = "Demo@2026!";
    private const string AccountingOfficeLogin = "accounting.office@uat.cashline.test";

    public static bool IsEnabled(IConfiguration configuration, IHostEnvironment environment) =>
        configuration.GetValue<bool?>("DemoUat:Enabled")
        ?? (environment.IsDevelopment() || environment.IsEnvironment("Testing"));

    public static object BuildBootstrapResponse(IConfiguration configuration)
    {
        var credentials = GetFormalUatUserSeeds()
            .Select(seed => new
            {
                label = seed.Label,
                displayName = seed.DisplayName,
                username = seed.LoginName,
                email = seed.Email,
                password = seed.Password,
                clientType = "WEB_PORTAL",
                userType = seed.UserType,
                branchId = seed.BranchId,
                partyId = seed.PartyId,
                roleCodes = seed.RoleCodes,
                workspace = seed.Workspace,
                summary = seed.Summary
            })
            .ToArray();

        return new
        {
            enabled = true,
            provider = configuration["Database:Provider"] ?? "Sqlite",
            credentials,
            notes = new[]
            {
                "CashLine UAT provides structured role-based access for operations, procurement, inventory, sales, finance, Islamic Finance, supplier, and customer journeys.",
                "Each UAT user account is provisioned with a dedicated email-based identity and role-aligned access scope.",
                "Use assigned maker and checker accounts independently so workflow controls, permissions, and approvals are validated realistically."
            }
        };
    }

    public static async Task InitializeAsync(IServiceProvider services, IConfiguration configuration, IHostEnvironment environment)
    {
        if (!IsEnabled(configuration, environment))
        {
            LogStartup(environment, "Demo UAT bootstrap skipped because it is disabled.");
            return;
        }

        LogStartup(environment, "Demo UAT bootstrap starting.");
        using var scope = services.CreateScope();
        LogStartup(environment, "Demo UAT scope created.");
        var dbContext = scope.ServiceProvider.GetRequiredService<CashLineErpDbContext>();
        LogStartup(environment, "CashLineErpDbContext resolved.");
        var resetOnStart = configuration.GetValue<bool?>("DemoUat:ResetDatabaseOnStart") ?? false;
        var autoSeed = configuration.GetValue<bool?>("DemoUat:AutoSeed") ?? true;
        LogStartup(environment, $"Demo UAT config resolved. resetOnStart={resetOnStart}, autoSeed={autoSeed}.");

        if (resetOnStart)
        {
            LogStartup(environment, "ResetDatabaseOnStart enabled. Deleting database.");
            await dbContext.Database.EnsureDeletedAsync();
            LogStartup(environment, "Database deleted.");
        }

        LogStartup(environment, "Ensuring database is created.");
        await dbContext.Database.EnsureCreatedAsync();
        LogStartup(environment, "Database ensure-created completed.");
        await EnsureBusinessPartyNationalIdSchemaAsync(dbContext);
        await EnsureIamFunctionSchemaAsync(dbContext);
        LogStartup(environment, "Ensuring demo users.");
        await EnsureDemoUsersAsync(dbContext);
        await BackfillMissingUserFunctionsAsync(dbContext);
        await EnsureAccountingOfficeClientAssignmentsAsync(dbContext);
        LogStartup(environment, "Demo users ensured.");

        if (!autoSeed)
        {
            LogStartup(environment, "Auto-seed disabled. Demo UAT bootstrap completed.");
            return;
        }

        LogStartup(environment, "Ensuring operational demo data.");
        await EnsureOperationalDemoDataAsync(scope.ServiceProvider, dbContext);
        LogStartup(environment, "Operational demo data ensured.");
        LogStartup(environment, "Ensuring Islamic Finance demo data.");
        await EnsureIslamicFinanceDemoDataAsync(scope.ServiceProvider, dbContext);
        LogStartup(environment, "Islamic Finance demo data ensured.");
        LogStartup(environment, "Demo UAT bootstrap completed.");
    }

    private static void LogStartup(IHostEnvironment environment, string message)
    {
        if (!environment.IsDevelopment() && !environment.IsEnvironment("Testing"))
        {
            return;
        }

        Console.Error.WriteLine($"[DemoUatBootstrapper] {message}");
    }

    private static async Task EnsureBusinessPartyNationalIdSchemaAsync(CashLineErpDbContext dbContext)
    {
        if (!dbContext.Database.IsRelational())
        {
            return;
        }

        // EnsureCreated does not alter existing SQLite databases, so we apply a minimal schema patch
        // to add the optional-but-unique firm national ID column.
        var connection = dbContext.Database.GetDbConnection();
        if (connection.State != System.Data.ConnectionState.Open)
        {
            await connection.OpenAsync();
        }

        bool HasColumn(string tableName, string columnName)
        {
            using var cmd = connection.CreateCommand();
            cmd.CommandText = $"PRAGMA table_info({tableName});";
            using var reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                var name = reader.GetString(1);
                if (string.Equals(name, columnName, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
            return false;
        }

        if (!HasColumn("bp_party", "firm_national_id"))
        {
            await dbContext.Database.ExecuteSqlRawAsync("ALTER TABLE bp_party ADD COLUMN firm_national_id TEXT NULL;");
        }

        // Enforce uniqueness when provided (NULLs allowed for MVP). Partial index supported by SQLite.
        await dbContext.Database.ExecuteSqlRawAsync(
            "CREATE UNIQUE INDEX IF NOT EXISTS ux_bp_party_firm_national_id ON bp_party(firm_national_id) WHERE firm_national_id IS NOT NULL AND firm_national_id <> '';");
    }

    private static async Task EnsureDemoUsersAsync(CashLineErpDbContext dbContext)
    {
        var seeds = GetAllUserSeeds().ToArray();
        var loginNames = seeds
            .Select(seed => seed.LoginName)
            .ToArray();
        var requiredRoleCodes = seeds
            .SelectMany(seed => seed.RoleCodes)
            .Append("SYSTEM_OWNER")
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToArray();
        await EnsureSystemOwnerRoleAsync(dbContext);
        await EnsureAccountingOfficeRolesAsync(dbContext);
        var roleIdByCode = await dbContext.Roles
            .Where(role => requiredRoleCodes.Contains(role.RoleCode))
            .ToDictionaryAsync(role => role.RoleCode, role => role.Id, StringComparer.OrdinalIgnoreCase);

        var existingUsers = await dbContext.Users
            .Where(user => loginNames.Contains(user.LoginName))
            .ToListAsync();

        var usersByLogin = existingUsers.ToDictionary(user => user.LoginName, StringComparer.OrdinalIgnoreCase);
        var nextUserId = await GetNextUserIdAsync(dbContext);
        var nextUserRoleId = await GetNextUserRoleIdAsync(dbContext);
        var nextUserFunctionId = await GetNextUserFunctionIdAsync(dbContext);

        foreach (var seed in seeds)
        {
            var desiredRoleCode = seed.RoleCodes.FirstOrDefault()
                ?? throw new InvalidOperationException($"Seed user '{seed.LoginName}' does not define a role code.");
            if (!roleIdByCode.TryGetValue(desiredRoleCode, out var desiredRoleId))
            {
                throw new InvalidOperationException($"Role code '{desiredRoleCode}' could not be resolved for seed user '{seed.LoginName}'.");
            }

            if (!usersByLogin.TryGetValue(seed.LoginName, out var user))
            {
                user = new ApplicationUser(
                    nextUserId,
                    seed.LoginName,
                    seed.DisplayName,
                    seed.UserType,
                    PasswordSecurity.HashPassword(seed.Password),
                    email: seed.Email,
                    branchId: seed.BranchId,
                    partyId: seed.PartyId);
                dbContext.Users.Add(user);
                usersByLogin[seed.LoginName] = user;
                nextUserId++;
            }

            var existingAssignments = await dbContext.UserRoles
                .Where(entry => entry.UserId == user.Id)
                .ToListAsync();

            var requiresRoleReset = existingAssignments.Count != 1
                || existingAssignments[0].RoleId != desiredRoleId
                || !existingAssignments[0].IsActive;

            if (!requiresRoleReset)
            {
                nextUserFunctionId = await EnsureUserFunctionsAsync(dbContext, user.Id, seed.RoleCodes, nextUserFunctionId);
                continue;
            }

            if (existingAssignments.Count > 0)
            {
                dbContext.UserRoles.RemoveRange(existingAssignments);
            }

            var assignment = new UserRole(user.Id, desiredRoleId, DateTime.UtcNow);
            var property = assignment.GetType().GetProperty("Id");
            property?.SetValue(assignment, nextUserRoleId++);
            dbContext.UserRoles.Add(assignment);
            nextUserFunctionId = await EnsureUserFunctionsAsync(dbContext, user.Id, seed.RoleCodes, nextUserFunctionId);
        }

        await dbContext.SaveChangesAsync();
    }

    private static async Task EnsureAccountingOfficeClientAssignmentsAsync(CashLineErpDbContext dbContext)
    {
        var accountingOfficeUser = await dbContext.Users
            .AsNoTracking()
            .SingleOrDefaultAsync(user => user.LoginName == AccountingOfficeLogin);
        if (accountingOfficeUser is null)
        {
            return;
        }

        // Give the Accounting Office user a small but realistic mixed client list for UAT.
        // These parties are global (shared IDs across the whole system), not accounting-office-only IDs.
        var desiredClientIds = new[] { 1L, 2L, 3L, 4L };
        var existingAssignments = await dbContext.AccountingOfficeClientAssignments
            .Where(entry => entry.AccountingOfficeUserId == accountingOfficeUser.Id)
            .ToListAsync();
        var activeClientIds = existingAssignments
            .Where(entry => entry.IsActive)
            .Select(entry => entry.ClientPartyId)
            .ToHashSet();

        foreach (var clientId in desiredClientIds.Where(id => !activeClientIds.Contains(id)))
        {
            dbContext.AccountingOfficeClientAssignments.Add(new AccountingOfficeClientAssignment(
                accountingOfficeUser.Id,
                clientId,
                createdByUserId: 6));
        }

        await dbContext.SaveChangesAsync();
    }

    private static async Task EnsureSystemOwnerRoleAsync(CashLineErpDbContext dbContext)
    {
        if (await dbContext.Roles.AnyAsync(role => role.RoleCode == "SYSTEM_OWNER"))
        {
            return;
        }

        var nextRoleId = await dbContext.Roles.AnyAsync()
            ? await dbContext.Roles.MaxAsync(role => role.Id) + 1
            : 1L;
        dbContext.Roles.Add(new Role(nextRoleId, "SYSTEM_OWNER", "System Owner", PortalCode.Internal, isChecker: true));
        await dbContext.SaveChangesAsync();
    }

    private static async Task EnsureAccountingOfficeRolesAsync(CashLineErpDbContext dbContext)
    {
        await EnsureRoleAsync(dbContext, "ACCOUNTING_OFFICE_USER", "Accounting Office User", PortalCode.Internal, isChecker: false);
        await EnsureRoleAsync(dbContext, "ACCOUNTING_OFFICE_SUPERVISOR", "Accounting Office Supervisor", PortalCode.Internal, isChecker: true);
    }

    private static async Task EnsureRoleAsync(CashLineErpDbContext dbContext, string roleCode, string roleName, string portalCode, bool isChecker)
    {
        if (await dbContext.Roles.AnyAsync(role => role.RoleCode == roleCode))
        {
            return;
        }

        var nextRoleId = await dbContext.Roles.AnyAsync()
            ? await dbContext.Roles.MaxAsync(role => role.Id) + 1
            : 1L;
        dbContext.Roles.Add(new Role(nextRoleId, roleCode, roleName, portalCode, isChecker));
        await dbContext.SaveChangesAsync();
    }

    private static async Task EnsureIamFunctionSchemaAsync(CashLineErpDbContext dbContext)
    {
        if (!dbContext.Database.IsRelational())
        {
            return;
        }

        await dbContext.Database.ExecuteSqlRawAsync("""
            CREATE TABLE IF NOT EXISTS sec_user_function (
                user_function_id INTEGER NOT NULL CONSTRAINT PK_sec_user_function PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL,
                function_code TEXT NOT NULL,
                effective_from TEXT NOT NULL,
                effective_to TEXT NULL,
                is_active INTEGER NOT NULL
            );
            """);

        await dbContext.Database.ExecuteSqlRawAsync("""
            CREATE TABLE IF NOT EXISTS sec_portal_menu_section (
                menu_section_id INTEGER NOT NULL CONSTRAINT PK_sec_portal_menu_section PRIMARY KEY AUTOINCREMENT,
                portal_code TEXT NOT NULL,
                user_type_code TEXT NOT NULL,
                section_code TEXT NOT NULL,
                section_label TEXT NOT NULL,
                display_order INTEGER NOT NULL,
                is_enabled INTEGER NOT NULL,
                updated_at TEXT NOT NULL,
                updated_by INTEGER NULL
            );
            """);

        await dbContext.Database.ExecuteSqlRawAsync("""
            CREATE TABLE IF NOT EXISTS sec_function_governance (
                function_governance_id INTEGER NOT NULL CONSTRAINT PK_sec_function_governance PRIMARY KEY AUTOINCREMENT,
                function_code TEXT NOT NULL,
                function_label TEXT NOT NULL,
                menu_section_code TEXT NOT NULL,
                portal_code TEXT NOT NULL,
                user_type_code TEXT NOT NULL,
                route TEXT NOT NULL,
                step_id TEXT NOT NULL,
                permission_code TEXT NOT NULL,
                rule_code TEXT NOT NULL,
                display_order INTEGER NOT NULL,
                is_enabled INTEGER NOT NULL,
                updated_at TEXT NOT NULL,
                updated_by INTEGER NULL
            );
            """);

        await dbContext.Database.ExecuteSqlRawAsync("""
            CREATE TABLE IF NOT EXISTS sec_accounting_office_client (
                assignment_id INTEGER NOT NULL CONSTRAINT PK_sec_accounting_office_client PRIMARY KEY AUTOINCREMENT,
                accounting_office_user_id INTEGER NOT NULL,
                client_party_id INTEGER NOT NULL,
                is_active INTEGER NOT NULL,
                created_at TEXT NOT NULL,
                created_by INTEGER NULL,
                revoked_at TEXT NULL,
                revoked_by INTEGER NULL,
                updated_at TEXT NULL,
                updated_by INTEGER NULL
            );
            """);

        await dbContext.Database.ExecuteSqlRawAsync("""
            CREATE TABLE IF NOT EXISTS ao_client_request (
                ao_req_id INTEGER NOT NULL CONSTRAINT PK_ao_client_request PRIMARY KEY AUTOINCREMENT,
                client_party_id INTEGER NOT NULL,
                assigned_office_user_id INTEGER NULL,
                req_no TEXT NOT NULL,
                req_type TEXT NOT NULL,
                subject_text TEXT NOT NULL,
                details_text TEXT NOT NULL,
                status_code TEXT NOT NULL,
                submitted_at TEXT NULL,
                submitted_by INTEGER NULL,
                reviewed_at TEXT NULL,
                reviewed_by INTEGER NULL,
                completed_at TEXT NULL,
                completed_by INTEGER NULL,
                completion_notes TEXT NULL,
                rejected_at TEXT NULL,
                rejected_by INTEGER NULL,
                rejection_reason TEXT NULL,
                created_at TEXT NOT NULL,
                created_by INTEGER NULL,
                updated_at TEXT NULL,
                updated_by INTEGER NULL
            );
            """);
    }

    private static async Task<long> EnsureUserFunctionsAsync(
        CashLineErpDbContext dbContext,
        long userId,
        IReadOnlyCollection<string> roleCodes,
        long nextUserFunctionId)
    {
        var enforceExactSeedFunctions = roleCodes.Any(roleCode =>
            string.Equals(roleCode, "SUPER_ADMIN", StringComparison.OrdinalIgnoreCase)
            || string.Equals(roleCode, "SYSTEM_OWNER", StringComparison.OrdinalIgnoreCase)
            || string.Equals(roleCode, "ACCOUNTING_OFFICE_USER", StringComparison.OrdinalIgnoreCase)
            || string.Equals(roleCode, "ACCOUNTING_OFFICE_SUPERVISOR", StringComparison.OrdinalIgnoreCase));

        var desiredFunctionCodes = roleCodes
            .SelectMany(ResolveSeedFunctionCodes)
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .Order(StringComparer.OrdinalIgnoreCase)
            .ToArray();

        var desiredFunctionSet = desiredFunctionCodes.ToHashSet(StringComparer.OrdinalIgnoreCase);

        var existingAssignments = await dbContext.UserFunctionAssignments
            .Where(entry => entry.UserId == userId)
            .ToListAsync();
        var activeExistingCodes = existingAssignments
            .Where(entry => entry.IsActive)
            .Select(entry => entry.FunctionCode)
            .ToHashSet(StringComparer.OrdinalIgnoreCase);

        foreach (var functionCode in desiredFunctionCodes.Where(code => !activeExistingCodes.Contains(code)))
        {
            var assignment = new UserFunctionAssignment(userId, functionCode, DateTime.UtcNow);
            assignment.GetType().GetProperty("Id")?.SetValue(assignment, nextUserFunctionId++);
            dbContext.UserFunctionAssignments.Add(assignment);
        }

        if (enforceExactSeedFunctions)
        {
            var now = DateTime.UtcNow;
            foreach (var assignment in existingAssignments.Where(entry => entry.IsActive && !desiredFunctionSet.Contains(entry.FunctionCode)))
            {
                assignment.Deactivate(now);
            }
        }

        return nextUserFunctionId;
    }

    private static async Task BackfillMissingUserFunctionsAsync(CashLineErpDbContext dbContext)
    {
        var users = await dbContext.Users
            .Where(user => user.IsActive)
            .ToListAsync();
        var roleCodeById = await dbContext.Roles
            .Where(role => role.IsActive)
            .ToDictionaryAsync(role => role.Id, role => role.RoleCode);
        var activeRoleAssignments = await dbContext.UserRoles
            .Where(entry => entry.IsActive)
            .ToListAsync();
        var activeRolesByUserId = activeRoleAssignments
            .GroupBy(entry => entry.UserId)
            .ToDictionary(
                group => group.Key,
                group => group
                    .Select(entry => roleCodeById.TryGetValue(entry.RoleId, out var roleCode) ? roleCode : null)
                    .Where(roleCode => roleCode != null)
                    .Select(roleCode => roleCode!)
                    .ToArray());
        var activeFunctionUserIds = await dbContext.UserFunctionAssignments
            .Where(entry => entry.IsActive)
            .Select(entry => entry.UserId)
            .Distinct()
            .ToArrayAsync();
        var usersWithActiveFunctions = activeFunctionUserIds.ToHashSet();
        var nextUserFunctionId = await GetNextUserFunctionIdAsync(dbContext);

        foreach (var user in users.Where(user => !usersWithActiveFunctions.Contains(user.Id)))
        {
            if (!activeRolesByUserId.TryGetValue(user.Id, out var roleCodes) || roleCodes.Length == 0)
            {
                continue;
            }

            nextUserFunctionId = await EnsureUserFunctionsAsync(dbContext, user.Id, roleCodes, nextUserFunctionId);
        }

        await dbContext.SaveChangesAsync();
    }

    private static async Task EnsureOperationalDemoDataAsync(IServiceProvider services, CashLineErpDbContext dbContext)
    {
        if (!await dbContext.PurchaseRequisitions.AnyAsync(entry => entry.PurposeText == "Operational replenishment request"))
        {
            var purchaseRequisitionService = services.GetRequiredService<IPurchaseRequisitionService>();
            var requisition = purchaseRequisitionService.CreatePurchaseRequisition(new CreatePurchaseRequisitionRequest(
                BranchId: 1,
                RequesterUserId: 1,
                RequisitionDate: new DateOnly(2026, 4, 5),
                NeedByDate: new DateOnly(2026, 4, 12),
                PurposeText: "Operational replenishment request",
                CurrencyCode: "EGP",
                Lines:
                [
                    new PurchaseRequisitionLineRequest(1001, 25m, "EA", new DateOnly(2026, 4, 12), 18m, "Warehouse replenishment line")
                ]));
            purchaseRequisitionService.SubmitPurchaseRequisition(requisition.PurchaseRequisitionId, 1);
        }

        if (!await dbContext.InventoryAdjustments.AnyAsync(entry => entry.Notes == "Inventory variance review"))
        {
            var inventoryAdjustmentService = services.GetRequiredService<IInventoryAdjustmentService>();
            var adjustment = inventoryAdjustmentService.CreateInventoryAdjustment(new CreateInventoryAdjustmentRequest(
                BranchId: 1,
                WarehouseId: 1,
                AdjustmentDate: new DateOnly(2026, 4, 5),
                ReasonCode: "COUNT_VARIANCE",
                Notes: "Inventory variance review",
                Lines:
                [
                    new CreateInventoryAdjustmentLineRequest(1001, null, 2m, "EA", 18m, "AVAILABLE", "INV-OPEN-0001", null, "Stock variance under review")
                ]));
            inventoryAdjustmentService.SubmitInventoryAdjustment(adjustment.InventoryAdjustmentId, 1);
            inventoryAdjustmentService.ApproveInventoryAdjustment(adjustment.InventoryAdjustmentId, 2);
            inventoryAdjustmentService.PostInventoryAdjustment(adjustment.InventoryAdjustmentId, 2);
        }

        if (!await dbContext.SalesOrders.AnyAsync(entry => entry.SalesOrderNumber == "SO-OPEN-0001"))
        {
            var salesOrderService = services.GetRequiredService<ISalesOrderService>();
            var invoiceService = services.GetRequiredService<IAccountsReceivableInvoiceService>();
            var receiptService = services.GetRequiredService<IAccountsReceivableReceiptService>();

            var order = salesOrderService.CreateSalesOrder(new CreateSalesOrderRequest(
                BranchId: 1,
                CustomerId: 2,
                OrderDate: new DateOnly(2026, 4, 5),
                CurrencyCode: "EGP",
                ExchangeRate: 1m,
                Lines:
                [
                    new CreateSalesOrderLineRequest(1001, 1, 3m, "EA", 32m, "VAT14")
                ]));
            salesOrderService.SubmitSalesOrder(order.SalesOrderId, 1);

            var invoice = invoiceService.CreateInvoice(new CreateAccountsReceivableInvoiceRequest(
                CustomerId: 2,
                SalesOrderId: order.SalesOrderId,
                DispatchOrderId: null,
                InvoiceType: "TAX",
                InvoiceDate: new DateOnly(2026, 4, 5),
                TaxDate: new DateOnly(2026, 4, 5),
                DueDate: new DateOnly(2026, 4, 20),
                CurrencyCode: "EGP",
                Lines:
                [
                    new CreateAccountsReceivableInvoiceLineRequest(null, 1001, 3m, "EA", 32m, "VAT14")
                ]));
            invoiceService.SubmitInvoice(invoice.AccountsReceivableInvoiceId, 1);
            invoice = invoiceService.ApproveInvoice(invoice.AccountsReceivableInvoiceId, 2);
            invoice = invoiceService.PostInvoice(invoice.AccountsReceivableInvoiceId, 2);

            var receipt = receiptService.CreateReceipt(new CreateAccountsReceivableReceiptRequest(
                CustomerId: 2,
                ReceiptDate: new DateOnly(2026, 4, 6),
                ReceiptMethod: "BANK_TRANSFER",
                CurrencyCode: "EGP",
                AmountAmount: invoice.TotalAmount,
                ReferenceNumber: "RCPT-OPEN-0001",
                Notes: "Customer receipt allocation",
                Allocations:
                [
                    new CreateAccountsReceivableReceiptAllocationRequest(invoice.AccountsReceivableInvoiceId, invoice.TotalAmount, new DateOnly(2026, 4, 6))
                ]));
            receiptService.SubmitReceipt(receipt.AccountsReceivableReceiptId, 1);
            receiptService.ApproveReceipt(receipt.AccountsReceivableReceiptId, 2);
            receiptService.PostReceipt(receipt.AccountsReceivableReceiptId, 2);
        }

        var integrationRegistryService = services.GetRequiredService<IIntegrationEndpointRegistryService>();
        var paymentChannelConfigurationService = services.GetRequiredService<IPaymentChannelConfigurationService>();

        await EnsureDemoCustomerCollectionChannelAsync(dbContext, integrationRegistryService, paymentChannelConfigurationService, "PAYMOB", "/checkout", "/checkout-callback");
        await EnsureDemoCustomerCollectionChannelAsync(dbContext, integrationRegistryService, paymentChannelConfigurationService, "FAWRY", "/collect", "/collect-callback");
    }

    private static async Task EnsureDemoCustomerCollectionChannelAsync(
        CashLineErpDbContext dbContext,
        IIntegrationEndpointRegistryService integrationService,
        IPaymentChannelConfigurationService paymentChannelService,
        string providerCode,
        string outboundRelativePath,
        string inboundRelativePath)
    {
        if (await dbContext.PaymentChannelConfigurations.AnyAsync(entry =>
                entry.IsActive
                && entry.UsageCode == "CUSTOMER_COLLECTION"
                && entry.IntegrationProviderCode == providerCode))
        {
            return;
        }

        var providerName = providerCode.ToUpperInvariant();
        var providerSlug = providerCode.ToLowerInvariant();

        var outbound = integrationService.CreateIntegrationEndpoint(new CashLineERP.Contracts.Integration.CreateIntegrationEndpointRequest(
            EndpointName: $"Demo {providerName} collection outbound",
            IntegrationProviderCode: providerName,
            EnvironmentCode: "UAT",
            DirectionCode: "OUTBOUND",
            TransportCode: "REST",
            HttpMethodCode: "POST",
            BaseUrl: "https://gateway.uat.example",
            RelativePath: outboundRelativePath,
            AuthenticationModeCode: "SIGNATURE",
            CredentialReference: $"secret://{providerSlug}/uat/key",
            TimeoutSeconds: 20,
            RetryPolicyCode: "STANDARD",
            MaxRetryCount: 2,
            IsIdempotencyKeyRequired: true,
            Notes: "Demo customer collection outbound endpoint"));
        integrationService.SubmitIntegrationEndpoint(outbound.IntegrationEndpointId, 1);
        integrationService.ApproveIntegrationEndpoint(outbound.IntegrationEndpointId, 2);
        outbound = integrationService.ActivateIntegrationEndpoint(outbound.IntegrationEndpointId, 2);

        var inbound = integrationService.CreateIntegrationEndpoint(new CashLineERP.Contracts.Integration.CreateIntegrationEndpointRequest(
            EndpointName: $"Demo {providerName} collection callback",
            IntegrationProviderCode: providerName,
            EnvironmentCode: "UAT",
            DirectionCode: "INBOUND",
            TransportCode: "REST",
            HttpMethodCode: "POST",
            BaseUrl: "https://gateway.uat.example",
            RelativePath: inboundRelativePath,
            AuthenticationModeCode: "SIGNATURE",
            CredentialReference: $"secret://{providerSlug}/uat/key",
            TimeoutSeconds: 20,
            RetryPolicyCode: "STANDARD",
            MaxRetryCount: 2,
            IsIdempotencyKeyRequired: true,
            Notes: "Demo customer collection callback endpoint"));
        integrationService.SubmitIntegrationEndpoint(inbound.IntegrationEndpointId, 1);
        integrationService.ApproveIntegrationEndpoint(inbound.IntegrationEndpointId, 2);
        inbound = integrationService.ActivateIntegrationEndpoint(inbound.IntegrationEndpointId, 2);

        var channel = paymentChannelService.CreatePaymentChannel(new CreatePaymentChannelConfigurationRequest(
            ChannelName: $"Demo {providerName} customer collection",
            BranchId: 1,
            IntegrationProviderCode: providerName,
            ChannelTypeCode: "CHECKOUT_LINK",
            UsageCode: "CUSTOMER_COLLECTION",
            CurrencyCode: "EGP",
            OutboundIntegrationEndpointId: outbound.IntegrationEndpointId,
            InboundIntegrationEndpointId: inbound.IntegrationEndpointId,
            SupportsCardTokenReferences: false,
            UsesWalletRuleEngine: false,
            RequiresCustomerReference: true,
            MinimumAmount: 1m,
            MaximumAmount: 5000m,
            SettlementLeadDays: 1,
            Notes: "Demo customer collection channel"));
        paymentChannelService.SubmitPaymentChannel(channel.PaymentChannelConfigurationId, 1);
        paymentChannelService.ApprovePaymentChannel(channel.PaymentChannelConfigurationId, 2);
        paymentChannelService.ActivatePaymentChannel(channel.PaymentChannelConfigurationId, 2);
    }

    private static async Task EnsureIslamicFinanceDemoDataAsync(IServiceProvider services, CashLineErpDbContext dbContext)
    {
        if (await dbContext.IslamicFinanceRequests.AnyAsync())
        {
            return;
        }

        var partnerService = services.GetRequiredService<IIslamicFinancePartnerService>();
        var productService = services.GetRequiredService<IIslamicFinanceProductService>();
        var facilityService = services.GetRequiredService<IIslamicFinanceFacilityService>();
        var templateService = services.GetRequiredService<IIslamicFinanceContractTemplateService>();
        var requestService = services.GetRequiredService<IIslamicFinanceRequestService>();
        var shariahReviewService = services.GetRequiredService<IShariahReviewService>();
        var murabahaCaseService = services.GetRequiredService<IMurabahaCaseService>();
        var murabahaContractService = services.GetRequiredService<IIslamicFinanceMurabahaContractService>();
        var disbursementService = services.GetRequiredService<IIslamicFinanceMurabahaDisbursementService>();
        var settlementService = services.GetRequiredService<IIslamicFinanceSettlementService>();

        var partner = partnerService.CreatePartnerLender(new CreateIslamicFinancePartnerLenderRequest
        {
            PartnerLenderPartyId = 1,
            DefaultCurrencyCode = "EGP",
            SupportsMurabaha = true,
            SupportsReceivablesFinance = false,
            SupportsTawarruq = false
        });
        partnerService.SubmitPartnerLender(partner.Id, 1);
        partnerService.ApprovePartnerLender(partner.Id, 2);
        partner = partnerService.ActivatePartnerLender(partner.Id, 2);

        var product = productService.CreateProductProfile(new CreateIslamicFinanceProductProfileRequest
        {
            ProductType = "MURABAHA",
            ProductCode = "MUR-OPEN-001",
            ProductName = "Murabaha Standard Product",
            RequiresShariahReview = true,
            AllowsPartnerFunding = true,
            AllowsCashLineFunding = false
        });
        productService.SubmitProductProfile(product.Id, 1);
        productService.ApproveProductProfile(product.Id, 2);
        product = productService.ActivateProductProfile(product.Id, 2);

        var facility = facilityService.CreateFacility(new CreateIslamicFinanceFacilityRequest
        {
            PartnerLenderId = partner.Id,
            CustomerPartyId = 2,
            ProductProfileId = product.Id,
            FacilityNumber = "FAC-OPEN-001",
            CurrencyCode = "EGP",
            ApprovedLimitAmount = 250000m,
            UtilizedAmount = 0m
        });
        facilityService.SubmitFacility(facility.Id, 1);
        facilityService.ApproveFacility(facility.Id, 2);
        facility = facilityService.ActivateFacility(facility.Id, 2);

        var template = templateService.CreateContractTemplate(new CreateIslamicFinanceContractTemplateRequest
        {
            ProductProfileId = product.Id,
            TemplateCode = "TPL-OPEN-001",
            TemplateName = "Murabaha Standard Template",
            LanguageCode = "EN"
        });
        templateService.SubmitContractTemplate(template.Id, 1);
        templateService.ApproveContractTemplate(template.Id, 2);
        template = templateService.ActivateContractTemplate(template.Id, 2);

        var request = requestService.CreateRequest(new CreateIslamicFinanceFinancingRequestRequest
        {
            RequestNumber = "REQ-OPEN-001",
            CustomerPartyId = 2,
            ProductProfileId = product.Id,
            FacilityId = facility.Id,
            FundingSourceMode = "PARTNER",
            SourceDocumentType = "SALES_ORDER",
            SourceDocumentId = 1,
            TransactionCurrencyCode = "EGP",
            RequestedAmount = 120000m
        });
        requestService.SubmitRequest(request.Id, 1);
        request = requestService.ApproveRequest(request.Id, 2);
        request = requestService.AssignPartnerLender(request.Id, new AssignIslamicFinancePartnerLenderRequest
        {
            PartnerLenderId = partner.Id
        }, 1);

        var decision = requestService.CreateDecision(request.Id, new CreateIslamicFinanceDecisionRequest
        {
            DecisionCode = "APPROVED",
            DecisionNotes = "Initial approval decision",
            DecidingPartyId = 2,
            PartnerLenderId = partner.Id
        });
        requestService.SubmitDecision(decision.Id, 1);
        requestService.ApproveDecision(decision.Id, 2);

        var review = shariahReviewService.CreateReviewCase(new CreateIslamicFinanceShariahReviewRequest
        {
            ReviewCaseNumber = "SHR-OPEN-001",
            ProductProfileId = product.Id,
            FinancingRequestId = request.Id,
            ReviewReasonCode = "SHARIAH_REVIEW"
        });
        shariahReviewService.SubmitReviewCase(review.Id, 1);
        shariahReviewService.RecommendOutcome(review.Id, new RecommendIslamicFinanceShariahOutcomeRequest
        {
            ReviewOutcomeCode = "APPROVED",
            ReviewNotes = "Shariah approval completed"
        }, 1);
        review = shariahReviewService.ApproveOutcome(review.Id, 2);
        request = requestService.AttachShariahReview(request.Id, new AttachIslamicFinanceShariahReviewRequest
        {
            ShariahReviewCaseId = review.Id
        }, 1);

        var murabahaCase = murabahaCaseService.CreateCase(new CreateIslamicFinanceMurabahaCaseRequest
        {
            CaseNumber = "MUR-OPEN-001",
            FinancingRequestId = request.Id,
            CustomerPartyId = request.CustomerPartyId,
            PartnerLenderId = partner.Id,
            SupplierPartyId = 1,
            ProductProfileId = product.Id,
            ContractTemplateId = template.Id,
            TransactionCurrencyCode = "EGP",
            PurchaseCostAmount = 100000m,
            ProfitAmount = 20000m
        });
        murabahaCaseService.SubmitCase(murabahaCase.Id, 1);
        murabahaCaseService.ApproveCase(murabahaCase.Id, 2);
        murabahaCase = murabahaCaseService.ActivateCase(murabahaCase.Id, 2);
        murabahaContractService.GenerateContract(murabahaCase.Id, 1, new GenerateIslamicFinanceMurabahaContractRequest());
        murabahaCaseService.GenerateSchedule(murabahaCase.Id, 1, new GenerateIslamicFinanceMurabahaScheduleRequest
        {
            InstallmentCount = 4,
            FirstDueDate = new DateOnly(2026, 5, 1),
            InstallmentIntervalMonths = 1
        });

        var disbursement = disbursementService.CreateDisbursement(new CreateIslamicFinanceMurabahaDisbursementRequest
        {
            MurabahaCaseId = murabahaCase.Id,
            DisbursementNumber = "DIS-OPEN-001",
            SupplierPartyId = 1,
            TargetType = "SUPPLIER_PAYMENT",
            InstructedAmount = 100000m,
            PlannedExecutionDate = new DateOnly(2026, 4, 20)
        });
        disbursementService.SubmitDisbursement(disbursement.Id, 1);
        disbursementService.ApproveDisbursement(disbursement.Id, 2);
        disbursement = disbursementService.ExecuteDisbursement(disbursement.Id, new ExecuteIslamicFinanceMurabahaDisbursementRequest
        {
            ExecutionReferenceNumber = "EXEC-OPEN-001",
            ExecutedAtUtc = new DateTime(2026, 4, 5, 10, 0, 0, DateTimeKind.Utc),
            ExecutedAmount = 100000m,
            ExecutionNotes = "Disbursement executed successfully"
        }, 2);

        var settlement = settlementService.CreateSettlement(new CreateIslamicFinanceSettlementRequest
        {
            SettlementNumber = "SET-OPEN-001",
            MurabahaCaseId = murabahaCase.Id,
            SettlementDate = new DateOnly(2026, 4, 21),
            SettlementCurrencyCode = "EGP",
            Lines =
            [
                new CreateIslamicFinanceSettlementLineRequest
                {
                    MurabahaDisbursementId = disbursement.Id,
                    SettledAmount = 100000m
                }
            ]
        });
        settlementService.SubmitSettlement(settlement.Id, 1);
        settlementService.ApproveSettlement(settlement.Id, 2);
        settlementService.CloseSettlement(settlement.Id, 2);
    }

    private static Task<long> GetNextUserIdAsync(CashLineErpDbContext dbContext) =>
        dbContext.Users.AnyAsync()
            .ContinueWith(task => task.Result ? dbContext.Users.Max(user => user.Id) + 1 : 1000L);

    private static Task<long> GetNextUserRoleIdAsync(CashLineErpDbContext dbContext) =>
        dbContext.UserRoles.AnyAsync()
            .ContinueWith(task => task.Result ? dbContext.UserRoles.Max(entry => entry.Id) + 1 : 1000L);

    private static Task<long> GetNextUserFunctionIdAsync(CashLineErpDbContext dbContext) =>
        dbContext.UserFunctionAssignments.AnyAsync()
            .ContinueWith(task => task.Result ? dbContext.UserFunctionAssignments.Max(entry => entry.Id) + 1 : 1000L);

    private static IReadOnlyCollection<string> ResolveSeedFunctionCodes(string roleCode) =>
        roleCode.ToUpperInvariant() switch
        {
            "SUPER_ADMIN" => new[] { "CREATE_USERS", "ASSIGN_ROLES", "CONFIGURE_USER_FUNCTIONS", "SUSPEND_USERS", "HOLD_USERS", "REVOKE_USERS", "DELETE_USERS", "EXPORT_USER_ADMIN_REPORTS" },
            "SYSTEM_OWNER" => new[] { "CREATE_ADMINISTRATORS", "GOVERN_ADMINISTRATORS", "EXPORT_ADMINISTRATOR_REPORTS" },
            "ACCOUNTING_OFFICE_USER" or "ACCOUNTING_OFFICE_SUPERVISOR" => new[]
            {
                "ACCOUNTING_OFFICE_DASHBOARD",
                "ACCOUNTING_OFFICE_CLIENT_ONBOARDING",
                "ACCOUNTING_OFFICE_CLIENT_FINANCE",
                "ACCOUNTING_OFFICE_ETA_SUBMISSIONS",
                "ACCOUNTING_OFFICE_REPORTING"
            },
            "SUPPLIER_ADMIN" or "EXTERNAL_SUPPLIER_USER" => new[] { "SUPPLIER_ONBOARDING", "SUPPLIER_DOCUMENTS", "SUPPLIER_PURCHASE_ORDERS", "SUPPLIER_INVOICES", "SUPPLIER_STATEMENTS", "SUPPLIER_NOTIFICATIONS" },
            "CUSTOMER_ADMIN" or "EXTERNAL_CUSTOMER_USER" => new[] { "CUSTOMER_ONBOARDING", "CUSTOMER_DOCUMENTS", "CUSTOMER_ORDERS", "CUSTOMER_PAYMENTS", "CUSTOMER_STATEMENTS", "CUSTOMER_NOTIFICATIONS" },
            "FINANCE_MAKER" => new[] { "FINANCE_PREPARATION", "PAYMENT_INITIATION", "STATEMENT_PREPARATION", "FINANCE_REPORTING" },
            "FINANCE_CHECKER" => new[] { "FINANCE_REVIEW", "PAYMENT_APPROVAL", "STATEMENT_APPROVAL", "FINANCE_REPORTING" },
            _ => new[] { "OPERATIONS_WORKBENCH", "ONBOARDING_REVIEW", "WORKFLOW_MONITORING", "OPERATIONAL_REPORTING" }
        };

    private static IEnumerable<DemoUserSeed> GetAllUserSeeds()
    {
        foreach (var seed in GetLegacyAutomationUserSeeds())
        {
            yield return seed;
        }

        foreach (var seed in GetFormalUatUserSeeds())
        {
            yield return seed;
        }
    }

    private static IEnumerable<DemoUserSeed> GetLegacyAutomationUserSeeds()
    {
        yield return new DemoUserSeed(
            "Operations maker",
            "Operations Maker",
            InternalMakerLogin,
            InternalPassword,
            "demo.ops@cashline.local",
            UserTypeCode.Internal,
            2,
            new[] { "ADMIN" },
            1,
            null,
            "operations",
            "Creates and submits operational transactions across procurement, inventory, and sales.");

        yield return new DemoUserSeed(
            "Procurement maker",
            "Procurement Maker",
            ProcurementMakerLogin,
            InternalPassword,
            "demo.procurement@cashline.local",
            UserTypeCode.Internal,
            2,
            new[] { "ADMIN" },
            1,
            null,
            "procurement",
            "Focused procurement persona for requisitions and sourcing workflow checks.");

        yield return new DemoUserSeed(
            "Inventory controller",
            "Inventory Controller",
            InventoryControllerLogin,
            InternalPassword,
            "demo.inventory@cashline.local",
            UserTypeCode.Internal,
            2,
            new[] { "ADMIN" },
            1,
            null,
            "inventory",
            "Runs stock adjustments and warehouse control scenarios.");

        yield return new DemoUserSeed(
            "Sales operations",
            "Sales Operations",
            SalesOperationsLogin,
            InternalPassword,
            "demo.sales@cashline.local",
            UserTypeCode.Internal,
            2,
            new[] { "ADMIN" },
            1,
            null,
            "sales",
            "Covers order capture, invoicing preparation, and customer transaction flow.");

        yield return new DemoUserSeed(
            "Finance maker",
            "Finance Maker",
            FinanceMakerLogin,
            InternalPassword,
            "demo.finance.maker@cashline.local",
            UserTypeCode.Internal,
            3,
            new[] { "FINANCE_MAKER" },
            1,
            null,
            "finance",
            "Prepares finance-side submissions, receipts, and reconciliation work.");

        yield return new DemoUserSeed(
            "Finance checker",
            "Finance Checker",
            InternalCheckerLogin,
            InternalPassword,
            "demo.finance.checker@cashline.local",
            UserTypeCode.Internal,
            4,
            new[] { "FINANCE_CHECKER" },
            1,
            null,
            "finance-checker",
            "Approves, posts, and signs off controlled finance and checker actions.");

        yield return new DemoUserSeed(
            "Islamic operations",
            "Islamic Finance Officer",
            IslamicOperationsLogin,
            InternalPassword,
            "demo.islamic@cashline.local",
            UserTypeCode.Internal,
            2,
            new[] { "ADMIN" },
            1,
            null,
            "islamic",
            "Runs Murabaha-first request, case, disbursement, and settlement journeys.");

        yield return new DemoUserSeed(
            "Supplier user",
            "Supplier User",
            SupplierLogin,
            ExternalPassword,
            "demo.supplier@cashline.local",
            UserTypeCode.External,
            5,
            new[] { "SUPPLIER_ADMIN" },
            null,
            1,
            "supplier",
            "Validates supplier-facing portal visibility and statement views.");

        yield return new DemoUserSeed(
            "Customer user",
            "Customer User",
            CustomerLogin,
            ExternalPassword,
            "demo.customer@cashline.local",
            UserTypeCode.External,
            6,
            new[] { "CUSTOMER_ADMIN" },
            null,
            2,
            "customer",
            "Validates customer-facing order, invoice, payment, and statement views.");
    }

    private static IEnumerable<DemoUserSeed> GetFormalUatUserSeeds()
    {
        yield return new DemoUserSeed(
            "UAT System Owner",
            "System Owner",
            "system.owner@uat.cashline.test",
            "Owner@12345",
            "system.owner@uat.cashline.test",
            UserTypeCode.Internal,
            7,
            new[] { "SYSTEM_OWNER" },
            1,
            null,
            "system-owner",
            "Creates and governs Administrator accounts only, with no business or operational module access.");

        yield return new DemoUserSeed(
            "UAT System Administrator",
            "System Administrator",
            "administrator@uat.cashline.test",
            "Admin@12345",
            "administrator@uat.cashline.test",
            UserTypeCode.Internal,
            1,
            new[] { "SUPER_ADMIN" },
            1,
            null,
            "administrator",
            "Manages users, role assignment, account lifecycle, and access governance across all portals.");

        yield return new DemoUserSeed(
            "UAT Operations Maker",
            "UAT Operations Maker",
            "operations.maker@uat.cashline.test",
            "OpsMaker@2026!",
            "operations.maker@uat.cashline.test",
            UserTypeCode.Internal,
            2,
            new[] { "ADMIN" },
            1,
            null,
            "operations",
            "Executes operational workflow scenarios across procurement, inventory, sales, and workflow control.");

        yield return new DemoUserSeed(
            "UAT Procurement Maker",
            "UAT Procurement Maker",
            "procurement.maker@uat.cashline.test",
            "Procure@2026!",
            "procurement.maker@uat.cashline.test",
            UserTypeCode.Internal,
            2,
            new[] { "ADMIN" },
            1,
            null,
            "procurement",
            "Executes sourcing, requisition, quotation, and procurement collaboration scenarios.");

        yield return new DemoUserSeed(
            "UAT Inventory Controller",
            "UAT Inventory Controller",
            "inventory.controller@uat.cashline.test",
            "Inventory@2026!",
            "inventory.controller@uat.cashline.test",
            UserTypeCode.Internal,
            2,
            new[] { "ADMIN" },
            1,
            null,
            "inventory",
            "Executes warehouse, adjustment, and stock-control validation scenarios.");

        yield return new DemoUserSeed(
            "UAT Sales Operations",
            "UAT Sales Operations",
            "sales.operations@uat.cashline.test",
            "SalesOps@2026!",
            "sales.operations@uat.cashline.test",
            UserTypeCode.Internal,
            2,
            new[] { "ADMIN" },
            1,
            null,
            "sales",
            "Executes order capture, customer fulfillment, and commercial transaction scenarios.");

        yield return new DemoUserSeed(
            "UAT Finance Maker",
            "UAT Finance Maker",
            "finance.maker@uat.cashline.test",
            "FinMaker@2026!",
            "finance.maker@uat.cashline.test",
            UserTypeCode.Internal,
            3,
            new[] { "FINANCE_MAKER" },
            1,
            null,
            "finance",
            "Prepares finance submissions, reconciliations, receipts, and reporting actions.");

        yield return new DemoUserSeed(
            "UAT Finance Checker",
            "UAT Finance Checker",
            "finance.checker@uat.cashline.test",
            "FinChecker@2026!",
            "finance.checker@uat.cashline.test",
            UserTypeCode.Internal,
            4,
            new[] { "FINANCE_CHECKER" },
            1,
            null,
            "finance-checker",
            "Executes approval, posting, close, and controlled financial sign-off scenarios.");

        yield return new DemoUserSeed(
            "UAT Islamic Finance Officer",
            "UAT Islamic Finance Officer",
            "islamic.finance.officer@uat.cashline.test",
            "IslamicOps@2026!",
            "islamic.finance.officer@uat.cashline.test",
            UserTypeCode.Internal,
            2,
            new[] { "ADMIN" },
            1,
            null,
            "islamic",
            "Executes Islamic Finance request, Murabaha, disbursement, settlement, and servicing review scenarios.");

        yield return new DemoUserSeed(
            "UAT Accounting Office User",
            "UAT Accounting Office User",
            AccountingOfficeLogin,
            "AccountingOffice@2026!",
            AccountingOfficeLogin,
            UserTypeCode.Internal,
            8,
            new[] { "ACCOUNTING_OFFICE_USER" },
            1,
            null,
            "accounting-office",
            "Executes governed accounting-office client onboarding, client servicing, statements, and ETA submission posture under CashLine operations control.");

        yield return new DemoUserSeed(
            "UAT Supplier Portal User",
            "UAT Supplier Portal User",
            "supplier.portal@uat.cashline.test",
            "SupplierUat@2026!",
            "supplier.portal@uat.cashline.test",
            UserTypeCode.External,
            5,
            new[] { "SUPPLIER_ADMIN" },
            null,
            1,
            "supplier",
            "Executes supplier onboarding, quotation, purchase-order, document, and financing portal scenarios.");

        yield return new DemoUserSeed(
            "UAT Customer Portal User",
            "UAT Customer Portal User",
            "customer.portal@uat.cashline.test",
            "CustomerUat@2026!",
            "customer.portal@uat.cashline.test",
            UserTypeCode.External,
            6,
            new[] { "CUSTOMER_ADMIN" },
            null,
            2,
            "customer",
            "Executes customer onboarding, service, payments, financing, and order-to-cash portal scenarios.");
    }

    private sealed record DemoUserSeed(
        string Label,
        string DisplayName,
        string LoginName,
        string Password,
        string Email,
        string UserType,
        long RoleId,
        string[] RoleCodes,
        long? BranchId,
        long? PartyId,
        string Workspace,
        string Summary);
}
