using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Interfaces;

namespace PowderCoating.Infrastructure.Data;

/// <summary>
/// EF Core DbContext for the Powder Coating Logix application.
/// <para>
/// Extends <see cref="IdentityDbContext{TUser}"/> so that ASP.NET Core Identity tables
/// (Users, Roles, Claims, etc.) live in the same database as all business entities.
/// </para>
/// <para>
/// Two global query filters are applied automatically to every LINQ query against tenant entities:
/// <list type="number">
///   <item><description>Soft-delete filter — rows whose <c>IsDeleted == true</c> are hidden from all queries by default.</description></item>
///   <item><description>Multi-tenancy filter — non-platform-admin users only see rows whose <c>CompanyId</c> matches their own company.</description></item>
/// </list>
/// Both filters are evaluated at query execution time (not model build time) by reading
/// <see cref="CurrentCompanyId"/> and <see cref="IsSuperAdmin"/> from the live HTTP context,
/// so tenant isolation is enforced automatically without any controller-level boilerplate.
/// Bypass either filter by calling <c>.IgnoreQueryFilters()</c> (or passing <c>ignoreQueryFilters: true</c>
/// to repository methods) — reserved for SuperAdmin operations and document-number generation.
/// </para>
/// </summary>
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IDataProtectionKeyContext
{
    private readonly IHttpContextAccessor? _httpContextAccessor;
    private readonly IServiceProvider? _serviceProvider;

    /// <summary>
    /// Parameterless constructor used by EF Core design-time tooling (migrations, scaffolding).
    /// The <see cref="IHttpContextAccessor"/> and <see cref="IServiceProvider"/> are not
    /// available at design time, so optional overloads are used for the full runtime path.
    /// </summary>
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }

    /// <summary>
    /// Runtime constructor used by the ASP.NET Core DI container.
    /// Receives the <paramref name="httpContextAccessor"/> so that global query filters can
    /// read the current user's <c>CompanyId</c> claim on each query, and the
    /// <paramref name="serviceProvider"/> so that <see cref="SaveChangesAsync"/> can resolve
    /// <see cref="ITenantContext"/> to auto-stamp <c>CompanyId</c> on new entities.
    /// </summary>
    public ApplicationDbContext(
        DbContextOptions<ApplicationDbContext> options,
        IHttpContextAccessor httpContextAccessor,
        IServiceProvider serviceProvider)
        : base(options)
    {
        _httpContextAccessor = httpContextAccessor;
        _serviceProvider = serviceProvider;
    }

    /// <summary>
    /// Resolves the company ID that should be used in global query filters for the current request.
    /// <para>
    /// Evaluated at query execution time (not at model build time), so every LINQ query issued
    /// during a request automatically scopes results to the correct tenant without any
    /// controller-level filtering code.
    /// </para>
    /// <para>
    /// Resolution order:
    /// <list type="number">
    ///   <item><description>If the user is not authenticated, returns <c>null</c> — filters will exclude all tenant rows from unauthenticated requests.</description></item>
    ///   <item><description>If the user is a SuperAdmin who is currently impersonating a company (session key <c>ImpersonatingCompanyId</c>), returns that company's ID so they see only impersonated tenant data.</description></item>
    ///   <item><description>Otherwise reads the <c>CompanyId</c> JWT/cookie claim written by <see cref="Identity.CustomUserClaimsPrincipalFactory"/> at login.</description></item>
    /// </list>
    /// </para>
    /// </summary>
    private int? CurrentCompanyId
    {
        get
        {
            if (_httpContextAccessor?.HttpContext?.User?.Identity?.IsAuthenticated != true)
                return null;

            // SuperAdmin impersonation override — checked before claims
            if (_httpContextAccessor.HttpContext.User.IsInRole("SuperAdmin"))
            {
                var overrideId = _httpContextAccessor.HttpContext.Session?.GetInt32("ImpersonatingCompanyId");
                if (overrideId.HasValue) return overrideId.Value;
            }

            var companyIdClaim = _httpContextAccessor.HttpContext.User.FindFirst("CompanyId")?.Value;
            if (companyIdClaim != null && int.TryParse(companyIdClaim, out int companyId))
                return companyId;

            // Authenticated but CompanyId claim is missing or invalid.
            // Return 0 (never a real company ID) so the global filter generates
            // "CompanyId = 0" which matches nothing — prevents null-comparison
            // ambiguity from leaking cross-tenant rows.
            return 0;
        }
    }

    /// <summary>
    /// Returns <c>true</c> when the currently authenticated user holds the <c>SuperAdmin</c> system role.
    /// Evaluated at query execution time so global filter expressions always reflect the live request user.
    /// SuperAdmins with this flag set to <c>true</c> and a non-demo company ID still see only their own
    /// company's operational data; only <see cref="IsPlatformAdmin"/> unlocks cross-tenant visibility.
    /// </summary>
    private bool IsSuperAdmin
    {
        get
        {
            return _httpContextAccessor?.HttpContext?.User?.IsInRole("SuperAdmin") == true;
        }
    }

    /// <summary>
    /// Returns <c>true</c> only for SuperAdmin users whose <c>CompanyId</c> is the platform demo
    /// company (ID&nbsp;1) or who have no company association at all (e.g., the break-glass account).
    /// <para>
    /// This distinction is critical: a SuperAdmin created for a specific tenant company
    /// (<c>CompanyId != 1</c>) is a "company SuperAdmin" — they can access platform settings but
    /// their queries are still scoped to their own company's operational data so they cannot
    /// accidentally view or mutate another tenant's records.
    /// </para>
    /// <para>
    /// Only platform admins (this property = <c>true</c>) bypass the CompanyId filter entirely and
    /// can query across all tenants — necessary for platform management screens such as the
    /// Companies list, Seed Data tool, and cross-tenant analytics.
    /// </para>
    /// </summary>
    private bool IsPlatformAdmin
    {
        get
        {
            // No HTTP context means background service, hosted service, or unit test — bypass tenant filter
            if (_httpContextAccessor?.HttpContext == null) return true;
            if (!IsSuperAdmin) return false;
            // CompanyId == 0 means no claim was present (break-glass / test SuperAdmins) — treat as platform admin
            return CurrentCompanyId == null || CurrentCompanyId == 0 || CurrentCompanyId == 1;
        }
    }

    // -------------------------------------------------------------------------
    // DbSet properties — one per entity type.
    // EF Core uses these to generate migration SQL and to resolve Set<T>() calls
    // made by the generic Repository<T>.  Adding a new entity requires both a
    // DbSet here AND a HasQueryFilter call in OnModelCreating.
    // -------------------------------------------------------------------------

    /// <summary>Tenant company records. Soft-delete only filter applies (no CompanyId filter — SuperAdmin manages all companies).</summary>
    public DbSet<Company> Companies { get; set; }

    /// <summary>Immutable audit trail of SMS terms-of-service acceptances per company. Tenant-filtered by CompanyId; never soft-deleted.</summary>
    public DbSet<CompanySmsAgreement> CompanySmsAgreements { get; set; }

    /// <summary>AI quote-item predictions; tenant-filtered. Both <c>QuoteItem</c> and <c>JobItem</c> share a single prediction record via nullable FK (no duplication on quote→job conversion).</summary>
    public DbSet<AiItemPrediction> AiItemPredictions { get; set; }

    /// <summary>Platform-wide log of every Anthropic API call. No tenant query filter — SuperAdmin reads across all companies for cost/abuse reporting.</summary>
    public DbSet<AiUsageLog> AiUsageLogs { get; set; }

    /// <summary>Per-coat powder consumption logs captured when a job coat is completed; tenant-filtered. Used by powder-usage analytics reports.</summary>
    public DbSet<PowderUsageLog> PowderUsageLogs { get; set; }

    // Core entities
    /// <summary>Commercial and non-commercial customer records; tenant-filtered with soft delete.</summary>
    public DbSet<Customer> Customers { get; set; }
    /// <summary>Job records progressing through the 16-status lifecycle; tenant-filtered with soft delete.</summary>
    public DbSet<Job> Jobs { get; set; }
    /// <summary>Per-day priority overrides that let supervisors re-order the shop floor queue without editing the job itself.</summary>
    public DbSet<JobDailyPriority> JobDailyPriorities { get; set; }
    /// <summary>Individual line-items on a job (custom work, catalog items, labor); tenant-filtered with soft delete.</summary>
    public DbSet<JobItem> JobItems { get; set; }
    /// <summary>Powder coat passes for a job item (color, thickness, coverage); tenant-filtered with soft delete.</summary>
    public DbSet<JobItemCoat> JobItemCoats { get; set; }
    /// <summary>Prep-service assignments on a job item (sandblasting, masking, etc.); tenant-filtered with soft delete.</summary>
    public DbSet<JobItemPrepService> JobItemPrepServices { get; set; }
    /// <summary>Immutable audit trail of every field change on a job; tenant-filtered with soft delete.</summary>
    public DbSet<JobChangeHistory> JobChangeHistories { get; set; }
    /// <summary>Clock-in / clock-out time entries for workers on a job; used for labor-cost calculations.</summary>
    public DbSet<JobTimeEntry> JobTimeEntries { get; set; }
    /// <summary>Quote records with multi-item pricing; tenant-filtered with soft delete.</summary>
    public DbSet<Quote> Quotes { get; set; }
    /// <summary>Photos uploaded during the AI Photo Quote workflow; tenant-filtered with soft delete.</summary>
    public DbSet<QuotePhoto> QuotePhotos { get; set; }
    /// <summary>Line-items on a quote; tenant-filtered with soft delete.</summary>
    public DbSet<QuoteItem> QuoteItems { get; set; }
    /// <summary>Powder coat passes for a quote item; tenant-filtered with soft delete.</summary>
    public DbSet<QuoteItemCoat> QuoteItemCoats { get; set; }
    /// <summary>Prep-service assignments on a quote item; tenant-filtered with soft delete.</summary>
    public DbSet<QuoteItemPrepService> QuoteItemPrepServices { get; set; }
    /// <summary>Immutable audit trail of every field change on a quote; tenant-filtered with soft delete.</summary>
    public DbSet<QuoteChangeHistory> QuoteChangeHistories { get; set; }
    /// <summary>Powder and material inventory items; tenant-filtered with soft delete.</summary>
    public DbSet<InventoryItem> InventoryItems { get; set; }
    /// <summary>Stock movement transactions (Purchase, Sale, Adjustment, Waste, etc.); tenant-filtered with soft delete.</summary>
    public DbSet<InventoryTransaction> InventoryTransactions { get; set; }
    /// <summary>User-defined inventory categories; stored as a lookup table (not an enum) so tenants can customise them.</summary>
    public DbSet<InventoryCategoryLookup> InventoryCategoryLookups { get; set; }
    /// <summary>Shop equipment (ovens, sandblasters, coating booths); tenant-filtered with soft delete.</summary>
    public DbSet<Equipment> Equipment { get; set; }
    /// <summary>Named blast setups per company (cabinet, pressure pot, blast room, etc.); tenant-filtered with soft delete.</summary>
    public DbSet<CompanyBlastSetup> CompanyBlastSetups { get; set; }
    /// <summary>Named oven cost configurations used by the Oven Scheduler; tenant-filtered with soft delete.</summary>
    public DbSet<OvenCost> OvenCosts { get; set; }
    /// <summary>Scheduled oven batches that group jobs for a single cure run; tenant-filtered with soft delete.</summary>
    public DbSet<OvenBatch> OvenBatches { get; set; }
    /// <summary>Individual job/item assignments within an oven batch; tenant-filtered with soft delete.</summary>
    public DbSet<OvenBatchItem> OvenBatchItems { get; set; }
    /// <summary>Equipment maintenance records (scheduled, in-progress, completed); tenant-filtered with soft delete.</summary>
    public DbSet<MaintenanceRecord> MaintenanceRecords { get; set; }
    /// <summary>Supplier/vendor records used by Purchasing and Accounts Payable; tenant-filtered with soft delete.</summary>
    public DbSet<Vendor> Vendors { get; set; }
/// <summary>Rework records tracking quality failures and remediation work against a job; tenant-filtered with soft delete.</summary>
    public DbSet<ReworkRecord> ReworkRecords { get; set; }
    /// <summary>Customer refund records; tenant-filtered with soft delete.</summary>
    public DbSet<Refund> Refunds { get; set; }
    /// <summary>Credit memos issued to customers (e.g., after a refund or rework); unique memo number per company.</summary>
    public DbSet<CreditMemo> CreditMemos { get; set; }
    /// <summary>Records of credit memos applied against specific invoices; tenant-filtered with soft delete.</summary>
    public DbSet<CreditMemoApplication> CreditMemoApplications { get; set; }
    /// <summary>Gift certificates issued by the company; certificate code is unique per company.</summary>
    public DbSet<GiftCertificate> GiftCertificates { get; set; }
    /// <summary>Redemption events against a gift certificate; tenant-filtered with soft delete.</summary>
    public DbSet<GiftCertificateRedemption> GiftCertificateRedemptions { get; set; }
    /// <summary>Photos attached to a job (before/after, quality-check, AI analysis); tenant-filtered with soft delete.</summary>
    public DbSet<JobPhoto> JobPhotos { get; set; }
    /// <summary>Free-text notes added to a job by staff; tenant-filtered with soft delete.</summary>
    public DbSet<JobNote> JobNotes { get; set; }
    /// <summary>Free-text notes added to a customer record by staff; tenant-filtered with soft delete.</summary>
    public DbSet<CustomerNote> CustomerNotes { get; set; }
    /// <summary>Additional contacts (billing, ops, drop-off) associated with a customer; tenant-filtered with soft delete.</summary>
    public DbSet<CustomerContact> CustomerContacts { get; set; }
    /// <summary>Inventory items marked as frequently used for a customer; shown on Customer Details for faster quoting.</summary>
    public DbSet<CustomerPreferredPowder> CustomerPreferredPowders { get; set; }
    /// <summary>Audit trail of every status transition on a job, referencing the lookup-table statuses.</summary>
    public DbSet<JobStatusHistory> JobStatusHistory { get; set; }
    /// <summary>Customer pricing tiers (Standard, Preferred, Premium); tenant-filtered with soft delete.</summary>
    public DbSet<PricingTier> PricingTiers { get; set; }
    /// <summary>Company-level operating cost rates (labor, equipment, overhead) used by the pricing engine; soft-delete only (no tenant filter — linked 1:1 to Company).</summary>
    public DbSet<CompanyOperatingCosts> CompanyOperatingCosts { get; set; }
    /// <summary>Company-level UI and workflow preferences; soft-delete only (no tenant filter — linked 1:1 to Company).</summary>
    public DbSet<CompanyPreferences> CompanyPreferences { get; set; }

    // Lookup tables (replacing enums)
    // Stored in the DB instead of C# enums so tenants can customise display names,
    // colours, and ordering without requiring a code deployment.
    /// <summary>Job status definitions (Pending → Delivered plus terminal states); unique on (CompanyId, StatusCode).</summary>
    public DbSet<JobStatusLookup> JobStatusLookups { get; set; }
    /// <summary>Job priority definitions (Low, Normal, High, Urgent, Rush); unique on (CompanyId, PriorityCode).</summary>
    public DbSet<JobPriorityLookup> JobPriorityLookups { get; set; }
    /// <summary>Quote status definitions (Draft, Sent, Approved, etc.); unique on (CompanyId, StatusCode).</summary>
    public DbSet<QuoteStatusLookup> QuoteStatusLookups { get; set; }
    /// <summary>Appointment status definitions; tenant-filtered with soft delete.</summary>
    public DbSet<AppointmentStatusLookup> AppointmentStatusLookups { get; set; }
    /// <summary>Appointment type definitions; tenant-filtered with soft delete.</summary>
    public DbSet<AppointmentTypeLookup> AppointmentTypeLookups { get; set; }
    /// <summary>Prep-service catalog entries (Sandblasting, Masking, etc.) shared across quotes and jobs.</summary>
    public DbSet<PrepService> PrepServices { get; set; }
    /// <summary>Many-to-many join table associating a <see cref="Quote"/> with selected prep services.</summary>
    public DbSet<QuotePrepService> QuotePrepServices { get; set; }
    /// <summary>Many-to-many join table associating a <see cref="Job"/> with selected prep services.</summary>
    public DbSet<JobPrepService> JobPrepServices { get; set; }

    /// <summary>Customer appointments (service, pickup, consultation); tenant-filtered with soft delete.</summary>
    public DbSet<Appointment> Appointments { get; set; }

    // Product Catalog
    /// <summary>Hierarchical categories for the service catalog; tenant-filtered with soft delete.</summary>
    public DbSet<CatalogCategory> CatalogCategories { get; set; }
    /// <summary>Pre-priced service catalog items that can be added to quotes/jobs; tenant-filtered with soft delete.</summary>
    public DbSet<CatalogItem> CatalogItems { get; set; }
    /// <summary>Most-recent AI price-check report per company; tenant-filtered with soft delete.</summary>
    public DbSet<CatalogPriceCheckReport> CatalogPriceCheckReports { get; set; }

    // Notifications
    /// <summary>Log of all outbound notifications (email, SMS, in-app) for audit and retry; tenant-filtered with soft delete.</summary>
    public DbSet<NotificationLog> NotificationLogs { get; set; }
    /// <summary>Per-company, per-channel notification template overrides; unique on (CompanyId, Type, Channel).</summary>
    public DbSet<NotificationTemplate> NotificationTemplates { get; set; }

    /// <summary>
    /// Stripe subscription plan configurations (limits, pricing, feature flags).
    /// Global — not tenant-scoped. Soft-delete only filter applied.
    /// Managed by platform admins via the Subscription Plans UI.
    /// </summary>
    public DbSet<SubscriptionPlanConfig> SubscriptionPlanConfigs { get; set; }

    /// <summary>
    /// Platform-level master list of powder coating products across all vendors.
    /// Not tenant-scoped — no global query filters applied.
    /// </summary>
    public DbSet<PowderCatalogItem> PowderCatalogItems { get; set; }

    /// <summary>Community library of shared formula templates. Platform-level, no tenant filter.</summary>
    public DbSet<FormulaLibraryItem> FormulaLibraryItems { get; set; }

    /// <summary>Per-company record of which community library formulas a company has imported.</summary>
    public DbSet<FormulaLibraryImport> FormulaLibraryImports { get; set; }

    /// <summary>Per-company thumbs-up / thumbs-down vote on community library formulas.</summary>
    public DbSet<FormulaLibraryRating> FormulaLibraryRatings { get; set; }

    /// <summary>User-submitted bug reports; tenant-filtered with soft delete.</summary>
    public DbSet<BugReport> BugReports { get; set; }
    /// <summary>File attachments for bug reports; soft-delete only (no tenant filter — access controlled via parent BugReport).</summary>
    public DbSet<BugReportAttachment> BugReportAttachments { get; set; }
    /// <summary>Contact Us form submissions; platform admins see all, company users see their own.</summary>
    public DbSet<ContactSubmission> ContactSubmissions { get; set; }

    // Invoices, Payments & Deposits
    /// <summary>Customer invoices (1:1 with Job enforced by unique index); tenant-filtered with soft delete.</summary>
    public DbSet<Invoice> Invoices { get; set; }
    /// <summary>Line-items on an invoice, with optional back-reference to the originating <see cref="JobItem"/>.</summary>
    public DbSet<InvoiceItem> InvoiceItems { get; set; }
    /// <summary>Customer payment records against an invoice; multiple partial payments supported.</summary>
    public DbSet<Payment> Payments { get; set; }
    /// <summary>
    /// Deposit records collected before a job is invoiced.
    /// Auto-applied to the invoice when it is created (unapplied deposits are swept into Payment records).
    /// </summary>
    public DbSet<Deposit> Deposits { get; set; }

    // Purchase Orders
    /// <summary>Purchase orders issued to vendors; tenant-filtered with soft delete.</summary>
    public DbSet<PurchaseOrder> PurchaseOrders { get; set; }
    /// <summary>Individual line-items on a purchase order; cascade-deleted when the PO is deleted.</summary>
    public DbSet<PurchaseOrderItem> PurchaseOrderItems { get; set; }

    // Expense Tracking / Accounts Payable
    /// <summary>Chart-of-accounts entries (Income, Expense, Asset, Liability); supports parent/child hierarchy via self-referencing FK.</summary>
    public DbSet<Account> Accounts { get; set; }
    /// <summary>Vendor bills (accounts payable); tenant-filtered with soft delete.</summary>
    public DbSet<Bill> Bills { get; set; }
    /// <summary>Line-items on a vendor bill, each assigned to a chart-of-accounts entry.</summary>
    public DbSet<BillLineItem> BillLineItems { get; set; }
    /// <summary>Payment records against a vendor bill; tracks cash disbursements to vendors.</summary>
    public DbSet<BillPayment> BillPayments { get; set; }
    /// <summary>Ad-hoc expense records (non-bill spending); tenant-filtered with soft delete.</summary>
    public DbSet<Expense> Expenses { get; set; }

    /// <summary>Manual double-entry journal entries (Draft/Posted/Reversed lifecycle); tenant-filtered with soft delete.</summary>
    public DbSet<JournalEntry> JournalEntries { get; set; }
    /// <summary>Individual debit/credit lines within a journal entry; soft-delete only (access controlled through parent JournalEntry).</summary>
    public DbSet<JournalEntryLine> JournalEntryLines { get; set; }

    /// <summary>Bank reconciliation sessions matching GL transactions to bank statements; tenant-filtered with soft delete.</summary>
    public DbSet<BankReconciliation> BankReconciliations { get; set; }

    /// <summary>Named tax rates used to pre-fill invoice tax percent by jurisdiction; tenant-filtered with soft delete.</summary>
    public DbSet<TaxRate> TaxRates { get; set; }

    /// <summary>Recurring transaction templates that auto-generate bills or expenses on a schedule; tenant-filtered with soft delete.</summary>
    public DbSet<RecurringTemplate> RecurringTemplates { get; set; }

    /// <summary>Fixed assets subject to straight-line depreciation; tenant-filtered with soft delete.</summary>
    public DbSet<FixedAsset> FixedAssets { get; set; }
    /// <summary>One record per asset per period for each depreciation posting; soft-delete only.</summary>
    public DbSet<FixedAssetDepreciationEntry> FixedAssetDepreciationEntries { get; set; }

    /// <summary>Named annual budgets with monthly amounts per GL account; tenant-filtered with soft delete.</summary>
    public DbSet<Budget> Budgets { get; set; }
    /// <summary>One row per account per Budget; contains Jan–Dec decimal columns.</summary>
    public DbSet<BudgetLine> BudgetLines { get; set; }
    /// <summary>Audit trail of completed year-end closes; tenant-filtered with soft delete.</summary>
    public DbSet<YearEndClose> YearEndCloses { get; set; }

    /// <summary>Credit notes received from vendors (returned goods, pricing disputes); tenant-filtered with soft delete.</summary>
    public DbSet<VendorCredit> VendorCredits { get; set; }
    /// <summary>Expense-reversal line items on a vendor credit; soft-delete only.</summary>
    public DbSet<VendorCreditLineItem> VendorCreditLineItems { get; set; }
    /// <summary>Application records linking a vendor credit to a specific bill; soft-delete only.</summary>
    public DbSet<VendorCreditApplication> VendorCreditApplications { get; set; }

    // Job Templates
    /// <summary>Reusable job templates that pre-populate job items, coats, and prep services on job creation.</summary>
    public DbSet<JobTemplate> JobTemplates { get; set; }
    /// <summary>Item definitions within a job template.</summary>
    public DbSet<JobTemplateItem> JobTemplateItems { get; set; }
    /// <summary>Coat definitions within a job template item.</summary>
    public DbSet<JobTemplateItemCoat> JobTemplateItemCoats { get; set; }
    /// <summary>Prep-service definitions within a job template item.</summary>
    public DbSet<JobTemplateItemPrepService> JobTemplateItemPrepServices { get; set; }

    // Customer Intake Kiosk
    /// <summary>Customer self-service intake sessions (walk-in tablet or remote email link); tenant-filtered with soft delete.</summary>
    public DbSet<KioskSession> KioskSessions { get; set; }

    // Custom Formula Templates
    /// <summary>Per-company reusable NCalc pricing formula templates; tenant-filtered with soft delete.</summary>
    public DbSet<CustomItemTemplate> CustomItemTemplates { get; set; }

    // Employee Timeclock
    /// <summary>Facility-level clock-in/clock-out entries per employee; tenant-filtered with soft delete. Multiple entries per day are supported (lunch breaks, etc.).</summary>
    public DbSet<EmployeeClockEntry> EmployeeClockEntries { get; set; }

    /// <summary>One row per activated kiosk tablet per company. Token stored in device cookie; delete row to revoke a device.</summary>
    public DbSet<TimeclockKioskDevice> TimeclockKioskDevices { get; set; }

    /// <summary>
    /// Platform-wide audit log capturing who changed what and when, across all tenants.
    /// No global query filter — SuperAdmin controllers query this directly.
    /// Indexed on (CompanyId, Timestamp) and (EntityType, EntityId) for efficient lookup.
    /// </summary>
    public DbSet<AuditLog> AuditLogs { get; set; }

    /// <summary>
    /// Platform-wide announcement banners shown to tenant users on login.
    /// No tenant filter — visibility controlled by the Target field (All, SpecificPlan, etc.) in the application layer.
    /// </summary>
    public DbSet<Announcement> Announcements { get; set; }
    /// <summary>Records of which users have dismissed which announcements; unique on (AnnouncementId, UserId).</summary>
    public DbSet<AnnouncementDismissal> AnnouncementDismissals { get; set; }

    /// <summary>Platform-wide release notes / changelog entries; no tenant filter.</summary>
    public DbSet<ReleaseNote> ReleaseNotes { get; set; }

    /// <summary>
    /// Global AI lookup patterns for resolving manufacturer product URLs.
    /// <c>CompanyId = 0</c> by convention (these are platform-level, not per-tenant).
    /// Soft-delete only filter applied; no tenant filter.
    /// </summary>
    public DbSet<ManufacturerLookupPattern> ManufacturerLookupPatterns { get; set; }

    /// <summary>
    /// Rotating dashboard tips shown in the welcome section.
    /// Platform-wide (seeded by <c>SeedDataService</c> with 40 tips); no tenant filter.
    /// </summary>
    public DbSet<DashboardTip> DashboardTips { get; set; }

    /// <summary>
    /// Platform-wide key/value configuration store (e.g., maintenance mode, feature flags).
    /// No tenant filter and no soft-delete — values are always current.
    /// Managed via the Platform Settings SuperAdmin UI.
    /// </summary>
    public DbSet<PlatformSetting> PlatformSettings { get; set; }

    /// <summary>
    /// ASP.NET Core Data Protection key ring — required by <see cref="IDataProtectionKeyContext"/>.
    /// Keys stored here survive deploys and IIS app pool recycles.
    /// </summary>
    public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }

    /// <summary>
    /// IP address ban list. Login attempts from a matching active entry are rejected
    /// before Identity even checks credentials. No tenant filter; SuperAdmin-managed only.
    /// </summary>
    public DbSet<BannedIp> BannedIps { get; set; }

    /// <summary>
    /// Stripe webhook event log for idempotency checking and replay debugging.
    /// Platform-wide; no tenant filter.
    /// </summary>
    public DbSet<StripeWebhookEvent> StripeWebhookEvents { get; set; }

    /// <summary>In-app notification bell entries; tenant-filtered with soft delete. The bell loads the last 20 records (read + unread).</summary>
    public DbSet<InAppNotification> InAppNotifications { get; set; }

    /// <summary>Records of users accepting the platform Terms of Service; used for compliance auditing.</summary>
    public DbSet<TermsAcceptance> TermsAcceptances { get; set; }

    /// <summary>
    /// Temporary Stripe checkout registration sessions created during the sign-up flow.
    /// No tenant filter — sessions exist before a Company record is created.
    /// Expired sessions are cleaned up by a background job.
    /// </summary>
    public DbSet<PendingRegistrationSession> PendingRegistrationSessions { get; set; }

    /// <summary>
    /// WebAuthn passkey credentials registered by users for biometric login (Face ID, fingerprint).
    /// No global query filter — the login flow queries by credentialId before authentication,
    /// requiring cross-tenant lookup. Per-user isolation is enforced in the controller.
    /// </summary>
    public DbSet<UserPasskey> UserPasskeys { get; set; }

    /// <summary>
    /// Configures the EF Core model: applies entity type configurations from the assembly,
    /// registers global query filters, defines relationships, adds performance indexes, and seeds
    /// static reference data.
    /// <para>
    /// Global query filters are set up here using lambda expressions that close over the
    /// <see cref="CurrentCompanyId"/> and <see cref="IsPlatformAdmin"/> properties.
    /// Because EF Core evaluates those property getters at query execution time (not at startup),
    /// each SQL query automatically includes the correct WHERE clause for the current HTTP request
    /// without any per-controller filtering code.
    /// </para>
    /// <para>
    /// Entities fall into three filter categories:
    /// <list type="bullet">
    ///   <item><description>Tenant-operational (most entities): <c>!IsDeleted AND (IsPlatformAdmin OR CompanyId == CurrentCompanyId)</c></description></item>
    ///   <item><description>Company-configuration (CompanyOperatingCosts, CompanyPreferences, SubscriptionPlanConfig): soft-delete only.</description></item>
    ///   <item><description>Platform-global (AuditLog, DashboardTip, PlatformSetting, etc.): no filter at all.</description></item>
    /// </list>
    /// </para>
    /// </summary>
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // Apply configurations
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);

        // Global query filters for soft deletes and multi-tenancy
        // These filters use properties that are evaluated at QUERY TIME, not model build time
        // This ensures proper per-request multi-tenancy isolation

        // Apply combined filters (soft delete + tenant isolation)
        // The CurrentCompanyId and IsSuperAdmin properties are evaluated for each query
        // Operational entities: only platform admins (demo company) bypass the company filter.
        // Company SuperAdmins (non-demo) see only their own company's records.
        modelBuilder.Entity<Customer>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<Job>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JobItem>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JobItemPrepService>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JobChangeHistory>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<Quote>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<AiItemPrediction>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<PowderUsageLog>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<QuoteItem>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<QuoteItemCoat>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<QuoteItemPrepService>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<QuoteChangeHistory>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<QuotePhoto>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<InventoryItem>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<InventoryTransaction>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<InventoryCategoryLookup>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<Equipment>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<MaintenanceRecord>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<Vendor>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<PricingTier>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JobPhoto>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JobNote>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<CustomerNote>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<CustomerPreferredPowder>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JobStatusHistory>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
modelBuilder.Entity<ReworkRecord>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<Refund>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<CreditMemo>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<CreditMemoApplication>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<GiftCertificate>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<GiftCertificateRedemption>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Job Templates
        modelBuilder.Entity<JobTemplate>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JobTemplateItem>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JobTemplateItemCoat>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JobTemplateItemPrepService>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Catalog/product entities: ALL SuperAdmins (both platform and company) bypass the
        // company filter so they can see the full manufacturer product catalog ("manu items").
        modelBuilder.Entity<CatalogCategory>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<CatalogItem>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<CatalogPriceCheckReport>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        modelBuilder.Entity<Appointment>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<AppointmentStatusLookup>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<NotificationLog>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<NotificationTemplate>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<AppointmentTypeLookup>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Lookup tables (company-specific)
        modelBuilder.Entity<JobStatusLookup>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JobPriorityLookup>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<QuoteStatusLookup>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<PrepService>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<QuotePrepService>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JobPrepService>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Company entity doesn't need tenant filter (SuperAdmin manages companies)
        modelBuilder.Entity<Company>().HasQueryFilter(e => !e.IsDeleted);

        // CompanyBlastSetups — tenant + soft-delete filter
        modelBuilder.Entity<CompanyBlastSetup>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // OvenCosts use tenant filter
        modelBuilder.Entity<OvenCost>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Oven Scheduling
        modelBuilder.Entity<OvenBatch>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<OvenBatchItem>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // CompanyOperatingCosts doesn't need tenant filter (linked to Company)
        modelBuilder.Entity<CompanyOperatingCosts>().HasQueryFilter(e => !e.IsDeleted);
        modelBuilder.Entity<CompanyPreferences>().HasQueryFilter(e => !e.IsDeleted);

        // SubscriptionPlanConfig is global (no tenant filter)
        modelBuilder.Entity<SubscriptionPlanConfig>().HasQueryFilter(e => !e.IsDeleted);

        // BugReports: platform admins see all, others see their own company's
        modelBuilder.Entity<BugReport>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<BugReportAttachment>().HasQueryFilter(e => !e.IsDeleted);
        modelBuilder.Entity<ContactSubmission>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Invoices, InvoiceItems, Payments, Deposits: tenant-filtered
        modelBuilder.Entity<Invoice>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<InvoiceItem>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<Payment>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<Deposit>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Deposit → Invoice (nullable, no cascade)
        modelBuilder.Entity<Deposit>()
            .HasOne(d => d.AppliedToInvoice)
            .WithMany()
            .HasForeignKey(d => d.AppliedToInvoiceId)
            .OnDelete(DeleteBehavior.SetNull);

        // Expense Tracking / Accounts Payable: tenant-filtered
        modelBuilder.Entity<Account>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<Bill>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<BillLineItem>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<BillPayment>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<Expense>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Journal Entries: tenant-filtered; lines use soft-delete only (child rows)
        modelBuilder.Entity<JournalEntry>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<JournalEntryLine>().HasQueryFilter(e => !e.IsDeleted);

        // Bank Reconciliation: tenant-filtered
        modelBuilder.Entity<BankReconciliation>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Tax Rates: tenant-filtered
        modelBuilder.Entity<TaxRate>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Recurring Templates: tenant-filtered
        modelBuilder.Entity<RecurringTemplate>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Fixed Assets: tenant-filtered with soft delete; depreciation entries soft-delete only
        modelBuilder.Entity<FixedAsset>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<FixedAssetDepreciationEntry>().HasQueryFilter(e => !e.IsDeleted);

        // FixedAsset → Account (three FKs): NoAction to avoid cascade conflicts; Account has no
        // reverse collection for FixedAssets so WithMany() is anonymous for each.
        modelBuilder.Entity<FixedAsset>()
            .HasOne(fa => fa.AssetAccount)
            .WithMany()
            .HasForeignKey(fa => fa.AssetAccountId)
            .OnDelete(DeleteBehavior.Restrict);
        modelBuilder.Entity<FixedAsset>()
            .HasOne(fa => fa.DepreciationExpenseAccount)
            .WithMany()
            .HasForeignKey(fa => fa.DepreciationExpenseAccountId)
            .OnDelete(DeleteBehavior.Restrict);
        modelBuilder.Entity<FixedAsset>()
            .HasOne(fa => fa.AccumDepreciationAccount)
            .WithMany()
            .HasForeignKey(fa => fa.AccumDepreciationAccountId)
            .OnDelete(DeleteBehavior.Restrict);

        // FixedAssetDepreciationEntry → JournalEntry: NoAction (entries outlive their JE)
        modelBuilder.Entity<FixedAssetDepreciationEntry>()
            .HasOne(e => e.JournalEntry)
            .WithMany()
            .HasForeignKey(e => e.JournalEntryId)
            .OnDelete(DeleteBehavior.NoAction);

        // Budgets: tenant-filtered; BudgetLines soft-delete only
        modelBuilder.Entity<Budget>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<BudgetLine>().HasQueryFilter(e => !e.IsDeleted);

        // BudgetLine → Account: Restrict delete so removing an account doesn't cascade into budget data
        modelBuilder.Entity<BudgetLine>()
            .HasOne(bl => bl.Account)
            .WithMany()
            .HasForeignKey(bl => bl.AccountId)
            .OnDelete(DeleteBehavior.Restrict);

        // YearEndClose: tenant-filtered; links to a specific JE
        modelBuilder.Entity<YearEndClose>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<YearEndClose>()
            .HasOne(y => y.JournalEntry)
            .WithMany()
            .HasForeignKey(y => y.JournalEntryId)
            .OnDelete(DeleteBehavior.Restrict);

        // Vendor Credits: tenant-filtered; child rows soft-delete only
        modelBuilder.Entity<VendorCredit>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<VendorCreditLineItem>().HasQueryFilter(e => !e.IsDeleted);
        modelBuilder.Entity<VendorCreditApplication>().HasQueryFilter(e => !e.IsDeleted);

        // VendorCreditApplication: NoAction on both FKs to avoid SQL Server multiple-cascade-path error 1785.
        // Bills and VendorCredits both cascade-delete through Vendor, creating two paths to VendorCreditApplications.
        modelBuilder.Entity<VendorCreditApplication>()
            .HasOne(vca => vca.Bill)
            .WithMany()
            .HasForeignKey(vca => vca.BillId)
            .OnDelete(DeleteBehavior.NoAction);
        modelBuilder.Entity<VendorCreditApplication>()
            .HasOne(vca => vca.VendorCredit)
            .WithMany(vc => vc.Applications)
            .HasForeignKey(vca => vca.VendorCreditId)
            .OnDelete(DeleteBehavior.NoAction);

        // Purchase Orders
        modelBuilder.Entity<PurchaseOrder>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<PurchaseOrderItem>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // ManufacturerLookupPatterns are global (CompanyId = 0); only soft-delete filter applies
        modelBuilder.Entity<ManufacturerLookupPattern>().HasQueryFilter(e => !e.IsDeleted);

        modelBuilder.Entity<InAppNotification>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Customer intake kiosk sessions — tenant-filtered + soft delete.
        // Anonymous intake routes must use ignoreQueryFilters:true when loading by SessionToken.
        modelBuilder.Entity<KioskSession>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<KioskSession>()
            .HasIndex(e => e.SessionToken)
            .IsUnique();
        modelBuilder.Entity<KioskSession>()
            .HasOne(k => k.LinkedCustomer)
            .WithMany()
            .HasForeignKey(k => k.LinkedCustomerId)
            .OnDelete(DeleteBehavior.SetNull);
        modelBuilder.Entity<KioskSession>()
            .HasOne(k => k.LinkedJob)
            .WithMany()
            .HasForeignKey(k => k.LinkedJobId)
            .OnDelete(DeleteBehavior.SetNull);

        // Custom Formula Templates — tenant-filtered + soft delete
        modelBuilder.Entity<CustomItemTemplate>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));

        // Employee Timeclock — tenant-filtered + soft delete
        modelBuilder.Entity<EmployeeClockEntry>().HasQueryFilter(e =>
            !e.IsDeleted && (IsPlatformAdmin || e.CompanyId == CurrentCompanyId));
        // FK to ApplicationUser: Restrict delete so removing a user doesn't erase attendance history.
        // Use DeleteBehavior.Restrict rather than NoAction to surface a cleaner error in EF.
        modelBuilder.Entity<EmployeeClockEntry>()
            .HasOne(c => c.User)
            .WithMany()
            .HasForeignKey(c => c.UserId)
            .OnDelete(DeleteBehavior.Restrict);
        // Composite index for "who's clocked in today" and date-range attendance reports
        modelBuilder.Entity<EmployeeClockEntry>()
            .HasIndex(c => new { c.CompanyId, c.ClockInTime });

        // Timeclock kiosk devices — one row per activated tablet per company
        modelBuilder.Entity<TimeclockKioskDevice>().HasQueryFilter(d =>
            !d.IsDeleted && (IsPlatformAdmin || d.CompanyId == CurrentCompanyId));
        modelBuilder.Entity<TimeclockKioskDevice>()
            .HasIndex(d => d.Token).IsUnique();
        modelBuilder.Entity<TimeclockKioskDevice>()
            .HasIndex(d => d.CompanyId);

        // Account self-referencing hierarchy
        modelBuilder.Entity<Account>()
            .HasOne(a => a.ParentAccount)
            .WithMany(a => a.SubAccounts)
            .HasForeignKey(a => a.ParentAccountId)
            .OnDelete(DeleteBehavior.Restrict);

        // JournalEntry self-referencing reversal link
        modelBuilder.Entity<JournalEntry>()
            .HasOne(je => je.ReversalOf)
            .WithMany()
            .HasForeignKey(je => je.ReversalOfId)
            .OnDelete(DeleteBehavior.Restrict);

        // BankReconciliation → Account (no cascade)
        modelBuilder.Entity<BankReconciliation>()
            .HasOne(br => br.Account)
            .WithMany()
            .HasForeignKey(br => br.AccountId)
            .OnDelete(DeleteBehavior.Restrict);

        // VendorCredit → APAccount (no cascade)
        modelBuilder.Entity<VendorCredit>()
            .HasOne(vc => vc.APAccount)
            .WithMany()
            .HasForeignKey(vc => vc.APAccountId)
            .OnDelete(DeleteBehavior.Restrict);

        // VendorCreditLineItem → Account (nullable, no cascade)
        modelBuilder.Entity<VendorCreditLineItem>()
            .HasOne(li => li.Account)
            .WithMany()
            .HasForeignKey(li => li.AccountId)
            .OnDelete(DeleteBehavior.Restrict);

        // Vendor → DefaultExpenseAccount (no cascade)
        modelBuilder.Entity<Vendor>()
            .HasOne(s => s.DefaultExpenseAccount)
            .WithMany()
            .HasForeignKey(s => s.DefaultExpenseAccountId)
            .OnDelete(DeleteBehavior.SetNull);

        // Vendor ↔ InventoryCategoryLookup (many-to-many supply categories)
        modelBuilder.Entity<Vendor>()
            .HasMany(v => v.Categories)
            .WithMany(c => c.Vendors)
            .UsingEntity<Dictionary<string, object>>(
                "VendorInventoryCategories",
                j => j.HasOne<InventoryCategoryLookup>().WithMany().HasForeignKey("InventoryCategoryLookupId"),
                j => j.HasOne<Vendor>().WithMany().HasForeignKey("VendorId"));

        // Bill → APAccount (no cascade to avoid cycles)
        modelBuilder.Entity<Bill>()
            .HasOne(b => b.APAccount)
            .WithMany(a => a.Bills)
            .HasForeignKey(b => b.APAccountId)
            .OnDelete(DeleteBehavior.Restrict);

        // BillLineItem → Account
        modelBuilder.Entity<BillLineItem>()
            .HasOne(li => li.Account)
            .WithMany(a => a.BillLineItems)
            .HasForeignKey(li => li.AccountId)
            .OnDelete(DeleteBehavior.Restrict);

        // BillPayment → BankAccount
        modelBuilder.Entity<BillPayment>()
            .HasOne(bp => bp.BankAccount)
            .WithMany(a => a.BillPayments)
            .HasForeignKey(bp => bp.BankAccountId)
            .OnDelete(DeleteBehavior.Restrict);

        // BillPayment → Vendor (no cascade; Bill already has FK to Vendor)
        modelBuilder.Entity<BillPayment>()
            .HasOne(bp => bp.Vendor)
            .WithMany(s => s.BillPayments)
            .HasForeignKey(bp => bp.VendorId)
            .OnDelete(DeleteBehavior.Restrict);

        // Expense → ExpenseAccount
        modelBuilder.Entity<Expense>()
            .HasOne(e => e.ExpenseAccount)
            .WithMany(a => a.Expenses)
            .HasForeignKey(e => e.ExpenseAccountId)
            .OnDelete(DeleteBehavior.Restrict);

        // Expense → PaymentAccount
        modelBuilder.Entity<Expense>()
            .HasOne(e => e.PaymentAccount)
            .WithMany(a => a.ExpensePaymentAccounts)
            .HasForeignKey(e => e.PaymentAccountId)
            .OnDelete(DeleteBehavior.Restrict);

        // Payment → DepositAccount (nullable, no cascade)
        modelBuilder.Entity<Payment>()
            .HasOne(p => p.DepositAccount)
            .WithMany()
            .HasForeignKey(p => p.DepositAccountId)
            .OnDelete(DeleteBehavior.NoAction);

        // Invoice → SalesTaxAccount (nullable, no cascade)
        modelBuilder.Entity<Invoice>()
            .HasOne(i => i.SalesTaxAccount)
            .WithMany()
            .HasForeignKey(i => i.SalesTaxAccountId)
            .OnDelete(DeleteBehavior.NoAction);

        // InvoiceItem → RevenueAccount (nullable, no cascade)
        modelBuilder.Entity<InvoiceItem>()
            .HasOne(ii => ii.RevenueAccount)
            .WithMany()
            .HasForeignKey(ii => ii.RevenueAccountId)
            .OnDelete(DeleteBehavior.NoAction);

        // InventoryItem → InventoryAccount / CogsAccount (nullable, no cascade — accounts use soft delete)
        modelBuilder.Entity<InventoryItem>()
            .HasOne(i => i.InventoryAccount)
            .WithMany()
            .HasForeignKey(i => i.InventoryAccountId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<InventoryItem>()
            .HasOne(i => i.CogsAccount)
            .WithMany()
            .HasForeignKey(i => i.CogsAccountId)
            .OnDelete(DeleteBehavior.NoAction);

        // CompanyPreferences → Default Revenue / COGS / Inventory accounts (nullable, no cascade,
        // no navigation property — accounts use soft delete and these are config pointers only).
        modelBuilder.Entity<CompanyPreferences>()
            .HasOne<Account>()
            .WithMany()
            .HasForeignKey(p => p.DefaultRevenueAccountId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<CompanyPreferences>()
            .HasOne<Account>()
            .WithMany()
            .HasForeignKey(p => p.DefaultCogsAccountId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<CompanyPreferences>()
            .HasOne<Account>()
            .WithMany()
            .HasForeignKey(p => p.DefaultInventoryAccountId)
            .OnDelete(DeleteBehavior.NoAction);

        // CatalogItem → RevenueAccount / CogsAccount (nullable, no cascade — accounts use soft delete)
        modelBuilder.Entity<CatalogItem>()
            .HasOne(ci => ci.RevenueAccount)
            .WithMany()
            .HasForeignKey(ci => ci.RevenueAccountId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<CatalogItem>()
            .HasOne(ci => ci.CogsAccount)
            .WithMany()
            .HasForeignKey(ci => ci.CogsAccountId)
            .OnDelete(DeleteBehavior.NoAction);

        // Performance indexes for frequently filtered non-FK columns
        // Jobs — dashboard and list views filter heavily on dates and status
        modelBuilder.Entity<Job>().HasIndex(j => j.DueDate);
        modelBuilder.Entity<Job>().HasIndex(j => j.ScheduledDate);
        modelBuilder.Entity<Job>().HasIndex(j => new { j.CompanyId, j.IsDeleted });

        // Quotes — dashboard filters on expiry; list views filter by status
        modelBuilder.Entity<Quote>().HasIndex(q => q.ExpirationDate);
        modelBuilder.Entity<Quote>().HasIndex(q => new { q.CompanyId, q.IsDeleted });

        // Invoices — dashboard and financial reports filter on status and dates
        modelBuilder.Entity<Invoice>().HasIndex(i => i.Status);
        modelBuilder.Entity<Invoice>().HasIndex(i => i.DueDate);
        modelBuilder.Entity<Invoice>().HasIndex(i => i.InvoiceDate);
        modelBuilder.Entity<Invoice>().HasIndex(i => new { i.CompanyId, i.IsDeleted });
        // Unique invoice number per company (includes soft-deleted to prevent reuse)
        modelBuilder.Entity<Invoice>()
            .HasIndex(i => new { i.CompanyId, i.InvoiceNumber })
            .IsUnique()
            .HasFilter(null); // enforce across all rows including soft-deleted

        // Unique memo number per company (prevents duplicate CM numbers across companies)
        modelBuilder.Entity<CreditMemo>()
            .HasIndex(m => new { m.CompanyId, m.MemoNumber })
            .IsUnique()
            .HasFilter(null);

        // Gift certificate codes must be unique per company (redemption is tenant-scoped via global query filter)
        modelBuilder.Entity<GiftCertificate>()
            .HasIndex(gc => new { gc.CompanyId, gc.CertificateCode })
            .IsUnique()
            .HasFilter(null);

        // Payments — dashboard aggregates by payment date
        modelBuilder.Entity<Payment>().HasIndex(p => p.PaymentDate);

        // Maintenance — dashboard filters on status and scheduled date
        modelBuilder.Entity<MaintenanceRecord>().HasIndex(m => m.Status);
        modelBuilder.Entity<MaintenanceRecord>().HasIndex(m => m.ScheduledDate);

        // Oven scheduler — filters batches by date and status every page load
        modelBuilder.Entity<OvenBatch>().HasIndex(o => new { o.ScheduledDate, o.Status });

        // Jobs — FK to status/priority lookups hit on every list/analytics query
        modelBuilder.Entity<Job>().HasIndex(j => j.JobStatusId);
        modelBuilder.Entity<Job>().HasIndex(j => j.JobPriorityId);

        // Quotes — FK to status lookup
        modelBuilder.Entity<Quote>().HasIndex(q => q.QuoteStatusId);

        // Bills — status and due-date filtering in AP reports and overdue detection
        modelBuilder.Entity<Bill>().HasIndex(b => b.Status);
        modelBuilder.Entity<Bill>().HasIndex(b => b.DueDate);
        modelBuilder.Entity<Bill>().HasIndex(b => new { b.CompanyId, b.Status });

        // Appointments — date + status filtering on list view and analytics
        modelBuilder.Entity<Appointment>().HasIndex(a => a.ScheduledStartTime);
        modelBuilder.Entity<Appointment>().HasIndex(a => new { a.CompanyId, a.AppointmentStatusId });

        // Inventory — active-flag filtering on list view and stats queries
        modelBuilder.Entity<InventoryItem>().HasIndex(i => i.IsActive);
        modelBuilder.Entity<InventoryItem>().HasIndex(i => new { i.CompanyId, i.IsActive });

        // Inventory transactions — powder usage analytics filters on type + date
        modelBuilder.Entity<InventoryTransaction>().HasIndex(t => new { t.TransactionType, t.TransactionDate });

        // AuditLog — no query filter; SuperAdmin controller queries directly
        modelBuilder.Entity<AuditLog>()
            .HasIndex(a => new { a.CompanyId, a.Timestamp });
        modelBuilder.Entity<AuditLog>()
            .HasIndex(a => new { a.EntityType, a.EntityId });

        // InAppNotification — bell endpoint fires on every page load; compound indexes let SQL
        // evaluate the tenant + soft-delete filter then sort/count without a full-table scan.
        modelBuilder.Entity<InAppNotification>()
            .HasIndex(n => new { n.CompanyId, n.IsDeleted, n.CreatedAt });
        modelBuilder.Entity<InAppNotification>()
            .HasIndex(n => new { n.CompanyId, n.IsDeleted, n.IsRead });

        // ContactSubmission — SuperAdmin inbox; filter + sort covered by a single index.
        modelBuilder.Entity<ContactSubmission>()
            .HasIndex(c => new { c.CompanyId, c.IsDeleted, c.CreatedAt });

        // Announcements — no tenant filter; visible based on Target logic in app layer
        modelBuilder.Entity<AnnouncementDismissal>()
            .HasOne(d => d.Announcement)
            .WithMany(a => a.Dismissals)
            .HasForeignKey(d => d.AnnouncementId)
            .OnDelete(DeleteBehavior.Cascade);
        modelBuilder.Entity<AnnouncementDismissal>()
            .HasIndex(d => new { d.AnnouncementId, d.UserId })
            .IsUnique();

        // Configure decimal precision
        foreach (var property in modelBuilder.Model.GetEntityTypes()
            .SelectMany(t => t.GetProperties())
            .Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
        {
            property.SetColumnType("decimal(18,2)");
        }

        // UserPasskey: unique index on CredentialId (WebAuthn requires global uniqueness)
        modelBuilder.Entity<UserPasskey>()
            .HasIndex(p => p.CredentialId)
            .IsUnique();

        // Configure relationships
        ConfigureRelationships(modelBuilder);

        // Seed initial data
        SeedInitialData(modelBuilder);
    }

    /// <summary>
    /// Configures all explicit EF Core relationships (FKs, navigation properties, delete behaviors)
    /// that cannot be inferred by convention or that require a non-default delete behavior.
    /// <para>
    /// Key design decisions encoded here:
    /// <list type="bullet">
    ///   <item><description><c>NoAction</c> delete is used extensively on audit / history tables so that deleting a parent entity does not erase the historical record.</description></item>
    ///   <item><description><c>Restrict</c> is used on operational FKs to prevent orphaned data (e.g., deleting a Vendor while POs exist).</description></item>
    ///   <item><description><c>SetNull</c> is used on optional navigations where the child can meaningfully exist without the parent (e.g., a Payment whose recorder has been deleted).</description></item>
    ///   <item><description><c>Cascade</c> is used only where child rows have no independent meaning (e.g., <c>InvoiceItem → Invoice</c>).</description></item>
    ///   <item><description>Self-referencing FKs (Account hierarchy, Job.OriginalJob) use <c>Restrict</c> or <c>NoAction</c> to avoid cycles.</description></item>
    /// </list>
    /// </para>
    /// </summary>
    private void ConfigureRelationships(ModelBuilder modelBuilder)
    {
        // AiUsageLog: FK to Company with no cascade so logs survive company deletion.
        // Composite index on (CompanyId, CalledAt) supports the SuperAdmin usage report efficiently.
        modelBuilder.Entity<AiUsageLog>()
            .HasOne(l => l.Company)
            .WithMany()
            .HasForeignKey(l => l.CompanyId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<AiUsageLog>()
            .HasIndex(l => new { l.CompanyId, l.CalledAt })
            .HasDatabaseName("IX_AiUsageLogs_CompanyId_CalledAt");

        // AiItemPrediction: QuoteItem and JobItem each carry a nullable FK pointing to the same
        // prediction record (shared when a quote converts to a job — no duplication).
        // No cascade delete: predictions are audit data and outlive their item records.
        modelBuilder.Entity<QuoteItem>()
            .HasOne(qi => qi.AiPrediction)
            .WithMany()
            .HasForeignKey(qi => qi.AiPredictionId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<JobItem>()
            .HasOne(ji => ji.AiPrediction)
            .WithMany()
            .HasForeignKey(ji => ji.AiPredictionId)
            .OnDelete(DeleteBehavior.NoAction);

        // PowderUsageLog relationships — no cascade delete (logs are permanent audit data)
        modelBuilder.Entity<PowderUsageLog>()
            .HasOne(l => l.Job)
            .WithMany()
            .HasForeignKey(l => l.JobId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<PowderUsageLog>()
            .HasOne(l => l.JobItem)
            .WithMany()
            .HasForeignKey(l => l.JobItemId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<PowderUsageLog>()
            .HasOne(l => l.JobItemCoat)
            .WithMany()
            .HasForeignKey(l => l.JobItemCoatId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<PowderUsageLog>()
            .HasOne(l => l.InventoryItem)
            .WithMany()
            .HasForeignKey(l => l.InventoryItemId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<PowderUsageLog>()
            .HasOne(l => l.InventoryTransaction)
            .WithMany()
            .HasForeignKey(l => l.InventoryTransactionId)
            .OnDelete(DeleteBehavior.NoAction);

        // Company relationships
        modelBuilder.Entity<ApplicationUser>()
            .HasOne(u => u.Company)
            .WithMany(c => c.Users)
            .HasForeignKey(u => u.CompanyId)
            .IsRequired(true)  // CompanyId is required, but navigation is nullable
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Customer>()
            .HasOne<Company>()
            .WithMany(c => c.Customers)
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Job>()
            .HasOne<Company>()
            .WithMany(c => c.Jobs)
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Equipment>()
            .HasOne<Company>()
            .WithMany(c => c.Equipment)
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Quote>()
            .HasOne<Company>()
            .WithMany(c => c.Quotes)
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<InventoryItem>()
            .HasOne<Company>()
            .WithMany(c => c.InventoryItems)
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Vendor>()
            .HasOne<Company>()
            .WithMany(c => c.Vendors)
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<PricingTier>()
            .HasOne<Company>()
            .WithMany(c => c.PricingTiers)
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        // CompanyOperatingCosts relationship (one-to-one)
        modelBuilder.Entity<CompanyOperatingCosts>()
            .HasOne(c => c.Company)
            .WithOne(c => c.OperatingCosts)
            .HasForeignKey<CompanyOperatingCosts>(c => c.CompanyId)
            .OnDelete(DeleteBehavior.Cascade);

        // CompanyPreferences relationship (one-to-one)
        modelBuilder.Entity<CompanyPreferences>()
            .HasOne(c => c.Company)
            .WithOne(c => c.Preferences)
            .HasForeignKey<CompanyPreferences>(c => c.CompanyId)
            .OnDelete(DeleteBehavior.Cascade);

        // Customer relationships
        modelBuilder.Entity<Customer>()
            .HasOne(c => c.PricingTier)
            .WithMany(p => p.Customers)
            .HasForeignKey(c => c.PricingTierId)
            .OnDelete(DeleteBehavior.SetNull);

        // Job relationships
        modelBuilder.Entity<Job>()
            .HasOne(j => j.Customer)
            .WithMany(c => c.Jobs)
            .HasForeignKey(j => j.CustomerId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Job>()
            .HasOne(j => j.Quote)
            .WithOne(q => q.ConvertedToJob)
            .HasForeignKey<Job>(j => j.QuoteId)
            .OnDelete(DeleteBehavior.SetNull);

        // JobChangeHistory relationships
        modelBuilder.Entity<JobChangeHistory>()
            .HasOne(h => h.Job)
            .WithMany()
            .HasForeignKey(h => h.JobId)
            .OnDelete(DeleteBehavior.Cascade);

        modelBuilder.Entity<JobChangeHistory>()
            .HasOne(h => h.ChangedBy)
            .WithMany()
            .HasForeignKey(h => h.ChangedByUserId)
            .IsRequired(false)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<JobChangeHistory>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(h => h.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        // Quote relationships
        modelBuilder.Entity<Quote>()
            .HasOne(q => q.Customer)
            .WithMany(c => c.Quotes)
            .HasForeignKey(q => q.CustomerId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Quote>()
            .HasOne(q => q.PreparedBy)
            .WithMany(u => u.PreparedQuotes)
            .HasForeignKey(q => q.PreparedById)
            .OnDelete(DeleteBehavior.SetNull);

        // CompanyBlastSetup relationships
        modelBuilder.Entity<CompanyBlastSetup>()
            .HasOne(b => b.Company)
            .WithMany()
            .HasForeignKey(b => b.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<QuoteItemPrepService>()
            .HasOne(q => q.BlastSetup)
            .WithMany()
            .HasForeignKey(q => q.BlastSetupId)
            .OnDelete(DeleteBehavior.SetNull);

        modelBuilder.Entity<JobItemPrepService>()
            .HasOne(j => j.BlastSetup)
            .WithMany()
            .HasForeignKey(j => j.BlastSetupId)
            .OnDelete(DeleteBehavior.SetNull);

        // OvenCost relationships
        modelBuilder.Entity<OvenCost>()
            .HasOne(o => o.Company)
            .WithMany()
            .HasForeignKey(o => o.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Quote>()
            .HasOne(q => q.OvenCost)
            .WithMany(o => o.Quotes)
            .HasForeignKey(q => q.OvenCostId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Job>()
            .HasOne(j => j.OvenCost)
            .WithMany(o => o.Jobs)
            .HasForeignKey(j => j.OvenCostId)
            .OnDelete(DeleteBehavior.Restrict);

        // QuoteChangeHistory relationships
        modelBuilder.Entity<QuoteChangeHistory>()
            .HasOne(h => h.Quote)
            .WithMany()
            .HasForeignKey(h => h.QuoteId)
            .OnDelete(DeleteBehavior.Cascade);

        modelBuilder.Entity<QuoteChangeHistory>()
            .HasOne(h => h.ChangedBy)
            .WithMany()
            .HasForeignKey(h => h.ChangedByUserId)
            .IsRequired(false)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<QuoteChangeHistory>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(h => h.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        // QuoteItemCoat relationships
        modelBuilder.Entity<QuoteItemCoat>()
            .HasOne(c => c.QuoteItem)
            .WithMany(qi => qi.Coats)
            .HasForeignKey(c => c.QuoteItemId)
            .OnDelete(DeleteBehavior.Cascade);

        // QuoteItemPrepService relationships
        modelBuilder.Entity<QuoteItemPrepService>()
            .HasOne(ps => ps.QuoteItem)
            .WithMany(qi => qi.PrepServices)
            .HasForeignKey(ps => ps.QuoteItemId)
            .OnDelete(DeleteBehavior.Cascade);

        modelBuilder.Entity<QuoteItemPrepService>()
            .HasOne(ps => ps.PrepService)
            .WithMany()
            .HasForeignKey(ps => ps.PrepServiceId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<QuoteItemPrepService>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(ps => ps.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<QuoteItemCoat>()
            .HasOne(c => c.InventoryItem)
            .WithMany()
            .HasForeignKey(c => c.InventoryItemId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<QuoteItemCoat>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(c => c.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        // Inventory relationships
        modelBuilder.Entity<InventoryItem>()
            .HasOne(i => i.PrimaryVendor)
            .WithMany(s => s.InventoryItems)
            .HasForeignKey(i => i.PrimaryVendorId)
            .OnDelete(DeleteBehavior.SetNull);

        modelBuilder.Entity<InventoryItem>()
            .HasOne(i => i.InventoryCategory)
            .WithMany(c => c.InventoryItems)
            .HasForeignKey(i => i.InventoryCategoryId)
            .OnDelete(DeleteBehavior.Restrict);

        // Equipment relationships
        modelBuilder.Entity<MaintenanceRecord>()
            .HasOne(m => m.Equipment)
            .WithMany(e => e.MaintenanceRecords)
            .HasForeignKey(m => m.EquipmentId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<MaintenanceRecord>()
            .HasOne(m => m.PerformedBy)
            .WithMany(u => u.PerformedMaintenances)
            .HasForeignKey(m => m.PerformedById)
            .OnDelete(DeleteBehavior.SetNull);



        modelBuilder.Entity<Job>()
            .HasOne(j => j.AssignedUser)
            .WithMany()
            .HasForeignKey(j => j.AssignedUserId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<MaintenanceRecord>()
            .HasOne(m => m.AssignedUser)
            .WithMany()
            .HasForeignKey(m => m.AssignedUserId)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<MaintenanceRecord>()
            .HasOne(m => m.RecurrenceParent)
            .WithMany()
            .HasForeignKey(m => m.RecurrenceParentId)
            .OnDelete(DeleteBehavior.ClientSetNull);

        modelBuilder.Entity<Appointment>()
            .HasOne(a => a.AssignedUser)
            .WithMany()
            .HasForeignKey(a => a.AssignedUserId)
            .OnDelete(DeleteBehavior.NoAction);

        // Catalog relationships
        modelBuilder.Entity<CatalogCategory>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<CatalogCategory>()
            .HasOne(c => c.ParentCategory)
            .WithMany(c => c.SubCategories)
            .HasForeignKey(c => c.ParentCategoryId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<CatalogItem>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<CatalogItem>()
            .HasOne(i => i.Category)
            .WithMany(c => c.Items)
            .HasForeignKey(i => i.CategoryId)
            .OnDelete(DeleteBehavior.Restrict);

        // Configure indexes
        // Multi-tenancy indexes
        modelBuilder.Entity<Customer>()
            .HasIndex(c => c.CompanyId);

        modelBuilder.Entity<Job>()
            .HasIndex(j => j.CompanyId);

        modelBuilder.Entity<Equipment>()
            .HasIndex(e => e.CompanyId);

        modelBuilder.Entity<Quote>()
            .HasIndex(q => q.CompanyId);

        modelBuilder.Entity<InventoryItem>()
            .HasIndex(i => i.CompanyId);

        modelBuilder.Entity<Vendor>()
            .HasIndex(s => s.CompanyId);

        modelBuilder.Entity<PricingTier>()
            .HasIndex(p => p.CompanyId);

modelBuilder.Entity<CatalogCategory>()
            .HasIndex(c => c.CompanyId);

        modelBuilder.Entity<CatalogCategory>()
            .HasIndex(c => c.ParentCategoryId);

        modelBuilder.Entity<CatalogItem>()
            .HasIndex(i => i.CompanyId);

        modelBuilder.Entity<CatalogItem>()
            .HasIndex(i => i.CategoryId);

        // Unique indexes (scoped to company where applicable)
        modelBuilder.Entity<Customer>()
            .HasIndex(c => new { c.CompanyId, c.Email })
            .IsUnique()
            .HasFilter("[Email] IS NOT NULL");

        modelBuilder.Entity<Customer>()
            .HasIndex(c => c.CompanyName);

        modelBuilder.Entity<Customer>()
            .Property(c => c.UnsubscribeToken)
            .HasDefaultValueSql("REPLACE(NEWID(),'-','')");

        modelBuilder.Entity<Customer>()
            .HasIndex(c => c.UnsubscribeToken)
            .IsUnique()
            .HasDatabaseName("IX_Customers_UnsubscribeToken");

        modelBuilder.Entity<Job>()
            .HasIndex(j => new { j.CompanyId, j.JobNumber })
            .IsUnique()
            .HasDatabaseName("IX_Jobs_CompanyId_JobNumber");

modelBuilder.Entity<Job>()
            .Property(j => j.ShopAccessCode)
            .HasDefaultValueSql("NEWID()");

        modelBuilder.Entity<Job>()
            .HasIndex(j => new { j.CompanyId, j.ShopAccessCode })
            .IsUnique()
            .HasDatabaseName("IX_Jobs_CompanyId_ShopAccessCode");

        modelBuilder.Entity<Quote>()
            .HasIndex(q => new { q.CompanyId, q.QuoteNumber })
            .IsUnique()
            .HasDatabaseName("IX_Quotes_CompanyId_QuoteNumber");

        modelBuilder.Entity<InventoryItem>()
            .HasIndex(i => new { i.CompanyId, i.SKU })
            .IsUnique()
            // Filter on IsDeleted so soft-deleted items don't reserve their SKU and block a new
            // (or re-created) item from reusing it — matching the app's soft-delete semantics.
            .HasFilter("[IsDeleted] = 0")
            .HasDatabaseName("IX_InventoryItems_CompanyId_SKU");

        modelBuilder.Entity<Company>()
            .HasIndex(c => c.CompanyCode)
            .IsUnique();

        // Lookup table relationships and indexes

        // JobStatusLookup relationships
        modelBuilder.Entity<JobStatusLookup>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Job>()
            .HasOne(j => j.JobStatus)
            .WithMany(s => s.Jobs)
            .HasForeignKey(j => j.JobStatusId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<JobStatusLookup>()
            .HasIndex(s => new { s.CompanyId, s.StatusCode })
            .IsUnique();

        modelBuilder.Entity<JobStatusLookup>()
            .HasIndex(s => s.CompanyId);

        // JobPriorityLookup relationships
        modelBuilder.Entity<JobPriorityLookup>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Job>()
            .HasOne(j => j.JobPriority)
            .WithMany(p => p.Jobs)
            .HasForeignKey(j => j.JobPriorityId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<JobPriorityLookup>()
            .HasIndex(p => new { p.CompanyId, p.PriorityCode })
            .IsUnique();

        modelBuilder.Entity<JobPriorityLookup>()
            .HasIndex(p => p.CompanyId);

        // QuoteStatusLookup relationships
        modelBuilder.Entity<QuoteStatusLookup>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Quote>()
            .HasOne(q => q.QuoteStatus)
            .WithMany(s => s.Quotes)
            .HasForeignKey(q => q.QuoteStatusId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<QuoteStatusLookup>()
            .HasIndex(s => new { s.CompanyId, s.StatusCode })
            .IsUnique();

        modelBuilder.Entity<QuoteStatusLookup>()
            .HasIndex(s => s.CompanyId);

        // ===================================================================
        // PERFORMANCE OPTIMIZATION: Composite Indexes for Frequent Queries
        // ===================================================================

        // Job composite indexes for filtering operations
        modelBuilder.Entity<Job>()
            .HasIndex(j => new { j.CompanyId, j.JobStatusId })
            .HasDatabaseName("IX_Jobs_CompanyId_JobStatusId");

        modelBuilder.Entity<Job>()
            .HasIndex(j => new { j.CompanyId, j.JobPriorityId })
            .HasDatabaseName("IX_Jobs_CompanyId_JobPriorityId");

        modelBuilder.Entity<Job>()
            .HasIndex(j => new { j.CompanyId, j.CustomerId })
            .HasDatabaseName("IX_Jobs_CompanyId_CustomerId");

        modelBuilder.Entity<Job>()
            .HasIndex(j => new { j.CompanyId, j.DueDate })
            .HasDatabaseName("IX_Jobs_CompanyId_DueDate");

        modelBuilder.Entity<Job>()
            .HasIndex(j => new { j.CompanyId, j.ScheduledDate })
            .HasDatabaseName("IX_Jobs_CompanyId_ScheduledDate");

        // Quote composite indexes
        modelBuilder.Entity<Quote>()
            .HasIndex(q => new { q.CompanyId, q.QuoteStatusId })
            .HasDatabaseName("IX_Quotes_CompanyId_QuoteStatusId");

        modelBuilder.Entity<Quote>()
            .HasIndex(q => new { q.CompanyId, q.ExpirationDate })
            .HasDatabaseName("IX_Quotes_CompanyId_ExpirationDate");

        modelBuilder.Entity<Quote>()
            .HasIndex(q => q.ApprovalToken)
            .IsUnique()
            .HasFilter("[ApprovalToken] IS NOT NULL")
            .HasDatabaseName("IX_Quotes_ApprovalToken");

        // Inventory composite indexes for low-stock queries
        modelBuilder.Entity<InventoryItem>()
            .HasIndex(i => new { i.CompanyId, i.QuantityOnHand, i.ReorderPoint })
            .HasDatabaseName("IX_InventoryItems_CompanyId_Quantity_Reorder");

        // Maintenance composite index
        modelBuilder.Entity<MaintenanceRecord>()
            .HasIndex(m => new { m.CompanyId, m.Status })
            .HasDatabaseName("IX_MaintenanceRecords_CompanyId_Status");

        modelBuilder.Entity<MaintenanceRecord>()
            .HasIndex(m => new { m.CompanyId, m.ScheduledDate })
            .HasDatabaseName("IX_MaintenanceRecords_CompanyId_ScheduledDate");

        // Appointment composite indexes for calendar queries
        modelBuilder.Entity<Appointment>()
            .HasIndex(a => new { a.CompanyId, a.ScheduledStartTime })
            .HasDatabaseName("IX_Appointments_CompanyId_ScheduledStartTime");

        modelBuilder.Entity<Appointment>()
            .HasIndex(a => new { a.CompanyId, a.AppointmentStatusId })
            .HasDatabaseName("IX_Appointments_CompanyId_AppointmentStatusId");

        // Equipment composite index for status filtering
        modelBuilder.Entity<Equipment>()
            .HasIndex(e => new { e.CompanyId, e.Status })
            .HasDatabaseName("IX_Equipment_CompanyId_Status");

        // JobItemPrepService relationships
        modelBuilder.Entity<JobItemPrepService>()
            .HasOne(ps => ps.JobItem)
            .WithMany(ji => ji.PrepServices)
            .HasForeignKey(ps => ps.JobItemId)
            .OnDelete(DeleteBehavior.Cascade);

        modelBuilder.Entity<JobItemPrepService>()
            .HasOne(ps => ps.PrepService)
            .WithMany()
            .HasForeignKey(ps => ps.PrepServiceId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<JobItemPrepService>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(ps => ps.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        // JobItem → CatalogItem relationship
        modelBuilder.Entity<JobItem>()
            .HasOne(ji => ji.CatalogItem)
            .WithMany()
            .HasForeignKey(ji => ji.CatalogItemId)
            .OnDelete(DeleteBehavior.SetNull);

        // JobItem composite index for job detail queries
        modelBuilder.Entity<JobItem>()
            .HasIndex(ji => new { ji.JobId, ji.IsDeleted })
            .HasDatabaseName("IX_JobItems_JobId_IsDeleted");

        // JobPhoto composite index for photo gallery queries
        modelBuilder.Entity<JobPhoto>()
            .HasIndex(jp => new { jp.JobId, jp.IsDeleted, jp.DisplayOrder })
            .HasDatabaseName("IX_JobPhotos_JobId_IsDeleted_DisplayOrder");

        // Additional frequently-queried foreign keys
        modelBuilder.Entity<JobItem>()
            .HasIndex(ji => ji.JobId)
            .HasDatabaseName("IX_JobItems_JobId");

        modelBuilder.Entity<QuoteItem>()
            .HasIndex(qi => qi.QuoteId)
            .HasDatabaseName("IX_QuoteItems_QuoteId");

        modelBuilder.Entity<QuoteItemCoat>()
            .HasIndex(qic => qic.QuoteItemId)
            .HasDatabaseName("IX_QuoteItemCoats_QuoteItemId");

        modelBuilder.Entity<QuoteItemCoat>()
            .HasIndex(qic => qic.InventoryItemId)
            .HasDatabaseName("IX_QuoteItemCoats_InventoryItemId");

        modelBuilder.Entity<QuoteItemCoat>()
            .HasIndex(qic => qic.CompanyId)
            .HasDatabaseName("IX_QuoteItemCoats_CompanyId");

        modelBuilder.Entity<JobItemPrepService>()
            .HasIndex(ps => ps.JobItemId)
            .HasDatabaseName("IX_JobItemPrepServices_JobItemId");

        modelBuilder.Entity<JobItemPrepService>()
            .HasIndex(ps => ps.PrepServiceId)
            .HasDatabaseName("IX_JobItemPrepServices_PrepServiceId");

        modelBuilder.Entity<JobItemPrepService>()
            .HasIndex(ps => ps.CompanyId)
            .HasDatabaseName("IX_JobItemPrepServices_CompanyId");

        modelBuilder.Entity<QuoteItemPrepService>()
            .HasIndex(qips => qips.QuoteItemId)
            .HasDatabaseName("IX_QuoteItemPrepServices_QuoteItemId");

        modelBuilder.Entity<QuoteItemPrepService>()
            .HasIndex(qips => qips.PrepServiceId)
            .HasDatabaseName("IX_QuoteItemPrepServices_PrepServiceId");

        modelBuilder.Entity<QuoteItemPrepService>()
            .HasIndex(qips => qips.CompanyId)
            .HasDatabaseName("IX_QuoteItemPrepServices_CompanyId");

        modelBuilder.Entity<JobNote>()
            .HasIndex(jn => new { jn.JobId, jn.CreatedAt })
            .HasDatabaseName("IX_JobNotes_JobId_CreatedAt");

        modelBuilder.Entity<CustomerNote>()
            .HasIndex(cn => new { cn.CustomerId, cn.CreatedAt })
            .HasDatabaseName("IX_CustomerNotes_CustomerId_CreatedAt");

        modelBuilder.Entity<CustomerPreferredPowder>()
            .HasIndex(p => new { p.CustomerId, p.InventoryItemId })
            .IsUnique()
            .HasDatabaseName("IX_CustomerPreferredPowders_CustomerId_InventoryItemId");

        modelBuilder.Entity<CustomerPreferredPowder>()
            .HasOne(p => p.Customer)
            .WithMany()
            .HasForeignKey(p => p.CustomerId)
            .OnDelete(DeleteBehavior.Cascade);

        modelBuilder.Entity<CustomerPreferredPowder>()
            .HasOne(p => p.InventoryItem)
            .WithMany()
            .HasForeignKey(p => p.InventoryItemId)
            .OnDelete(DeleteBehavior.Restrict);

        // ===================================================================
        // END PERFORMANCE OPTIMIZATION INDEXES
        // ===================================================================

        // InventoryCategoryLookup relationships
        modelBuilder.Entity<InventoryCategoryLookup>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(e => e.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<InventoryCategoryLookup>()
            .HasIndex(c => new { c.CompanyId, c.CategoryCode })
            .IsUnique();

        modelBuilder.Entity<InventoryCategoryLookup>()
            .HasIndex(c => c.CompanyId);

        // JobStatusHistory relationships (with lookup tables)
        modelBuilder.Entity<JobStatusHistory>()
            .HasOne(h => h.FromStatus)
            .WithMany(s => s.FromStatusHistory)
            .HasForeignKey(h => h.FromStatusId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<JobStatusHistory>()
            .HasOne(h => h.ToStatus)
            .WithMany(s => s.ToStatusHistory)
            .HasForeignKey(h => h.ToStatusId)
            .OnDelete(DeleteBehavior.Restrict);

        // NotificationLog relationships
        modelBuilder.Entity<NotificationLog>()
            .HasOne(n => n.Customer)
            .WithMany(c => c.NotificationLogs)
            .HasForeignKey(n => n.CustomerId)
            .OnDelete(DeleteBehavior.SetNull);

        modelBuilder.Entity<NotificationLog>()
            .HasOne(n => n.Job)
            .WithMany()
            .HasForeignKey(n => n.JobId)
            .OnDelete(DeleteBehavior.SetNull);

        modelBuilder.Entity<NotificationLog>()
            .HasOne(n => n.Quote)
            .WithMany()
            .HasForeignKey(n => n.QuoteId)
            .OnDelete(DeleteBehavior.SetNull);

        modelBuilder.Entity<NotificationLog>()
            .HasOne(n => n.Invoice)
            .WithMany()
            .HasForeignKey(n => n.InvoiceId)
            .OnDelete(DeleteBehavior.SetNull);

        // Invoice relationships
        modelBuilder.Entity<Invoice>()
            .HasOne(i => i.Job)
            .WithOne(j => j.Invoice)
            .HasForeignKey<Invoice>(i => i.JobId)
            .IsRequired(false)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Invoice>()
            .HasOne(i => i.Customer)
            .WithMany(c => c.Invoices)
            .HasForeignKey(i => i.CustomerId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Invoice>()
            .HasOne(i => i.PreparedBy)
            .WithMany()
            .HasForeignKey(i => i.PreparedById)
            .OnDelete(DeleteBehavior.SetNull);

        modelBuilder.Entity<Invoice>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(i => i.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        // InvoiceItem relationships
        modelBuilder.Entity<InvoiceItem>()
            .HasOne(ii => ii.Invoice)
            .WithMany(i => i.InvoiceItems)
            .HasForeignKey(ii => ii.InvoiceId)
            .OnDelete(DeleteBehavior.Cascade);

        modelBuilder.Entity<InvoiceItem>()
            .HasOne(ii => ii.SourceJobItem)
            .WithMany()
            .HasForeignKey(ii => ii.SourceJobItemId)
            .OnDelete(DeleteBehavior.SetNull);

        modelBuilder.Entity<InvoiceItem>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(ii => ii.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        // Payment relationships
        modelBuilder.Entity<Payment>()
            .HasOne(p => p.Invoice)
            .WithMany(i => i.Payments)
            .HasForeignKey(p => p.InvoiceId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<Payment>()
            .HasOne(p => p.RecordedBy)
            .WithMany()
            .HasForeignKey(p => p.RecordedById)
            .OnDelete(DeleteBehavior.SetNull);

        modelBuilder.Entity<Payment>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(p => p.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        // Invoice indexes
        modelBuilder.Entity<Invoice>()
            .HasIndex(i => new { i.CompanyId, i.JobId })
            .IsUnique()
            .HasDatabaseName("IX_Invoices_CompanyId_JobId");

        modelBuilder.Entity<Invoice>()
            .HasIndex(i => new { i.CompanyId, i.InvoiceNumber })
            .IsUnique()
            .HasDatabaseName("IX_Invoices_CompanyId_InvoiceNumber");

        modelBuilder.Entity<Invoice>()
            .HasIndex(i => new { i.CompanyId, i.Status })
            .HasDatabaseName("IX_Invoices_CompanyId_Status");

        modelBuilder.Entity<Invoice>()
            .HasIndex(i => new { i.CompanyId, i.ExternalReference })
            .HasDatabaseName("IX_Invoices_CompanyId_ExternalReference");

        modelBuilder.Entity<Invoice>()
            .HasIndex(i => new { i.CompanyId, i.DueDate })
            .HasDatabaseName("IX_Invoices_CompanyId_DueDate");

        modelBuilder.Entity<Invoice>()
            .HasIndex(i => new { i.CompanyId, i.CustomerId })
            .HasDatabaseName("IX_Invoices_CompanyId_CustomerId");

        modelBuilder.Entity<InvoiceItem>()
            .HasIndex(ii => ii.InvoiceId)
            .HasDatabaseName("IX_InvoiceItems_InvoiceId");

        modelBuilder.Entity<Payment>()
            .HasIndex(p => p.InvoiceId)
            .HasDatabaseName("IX_Payments_InvoiceId");

        modelBuilder.Entity<Payment>()
            .HasIndex(p => new { p.CompanyId, p.PaymentDate })
            .HasDatabaseName("IX_Payments_CompanyId_PaymentDate");

        modelBuilder.Entity<NotificationLog>()
            .HasOne<Company>()
            .WithMany()
            .HasForeignKey(n => n.CompanyId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<NotificationLog>()
            .HasIndex(n => new { n.CompanyId, n.SentAt })
            .HasDatabaseName("IX_NotificationLogs_CompanyId_SentAt");

        modelBuilder.Entity<NotificationLog>()
            .HasIndex(n => new { n.CompanyId, n.Status })
            .HasDatabaseName("IX_NotificationLogs_CompanyId_Status");

        // NotificationTemplate relationships
        modelBuilder.Entity<NotificationTemplate>()
            .HasOne(t => t.Company)
            .WithMany()
            .HasForeignKey(t => t.CompanyId)
            .OnDelete(DeleteBehavior.Cascade);

        modelBuilder.Entity<NotificationTemplate>()
            .HasIndex(t => new { t.CompanyId, t.NotificationType, t.Channel })
            .IsUnique()
            .HasDatabaseName("IX_NotificationTemplates_Company_Type_Channel");

        // PowderCatalogItem — platform-level, no tenant filter, unique on (VendorName, Sku)
        modelBuilder.Entity<PowderCatalogItem>()
            .HasIndex(p => new { p.VendorName, p.Sku })
            .IsUnique()
            .HasDatabaseName("IX_PowderCatalogItems_Vendor_Sku");

        modelBuilder.Entity<PowderCatalogItem>()
            .HasIndex(p => p.ColorName)
            .HasDatabaseName("IX_PowderCatalogItems_ColorName");

        // OvenBatch → Equipment (nullable, legacy — batches are historical records)
        modelBuilder.Entity<OvenBatch>()
            .HasOne(b => b.Equipment)
            .WithMany(e => e.OvenBatches)
            .HasForeignKey(b => b.EquipmentId)
            .IsRequired(false)
            .OnDelete(DeleteBehavior.Restrict);

        // OvenBatch → OvenCost (new — scheduler uses Named Ovens)
        modelBuilder.Entity<OvenBatch>()
            .HasOne(b => b.OvenCost)
            .WithMany()
            .HasForeignKey(b => b.OvenCostId)
            .IsRequired(false)
            .OnDelete(DeleteBehavior.Restrict);

        // OvenBatchItem → OvenBatch (cascade delete items when batch deleted)
        modelBuilder.Entity<OvenBatchItem>()
            .HasOne(i => i.Batch)
            .WithMany(b => b.Items)
            .HasForeignKey(i => i.OvenBatchId)
            .OnDelete(DeleteBehavior.Cascade);

        // OvenBatchItem → Job (no cascade)
        modelBuilder.Entity<OvenBatchItem>()
            .HasOne(i => i.Job)
            .WithMany()
            .HasForeignKey(i => i.JobId)
            .OnDelete(DeleteBehavior.NoAction);

        // OvenBatchItem → JobItem (no cascade)
        modelBuilder.Entity<OvenBatchItem>()
            .HasOne(i => i.JobItem)
            .WithMany()
            .HasForeignKey(i => i.JobItemId)
            .OnDelete(DeleteBehavior.NoAction);

        // OvenBatchItem → JobItemCoat (no cascade)
        modelBuilder.Entity<OvenBatchItem>()
            .HasOne(i => i.JobItemCoat)
            .WithMany()
            .HasForeignKey(i => i.JobItemCoatId)
            .OnDelete(DeleteBehavior.NoAction);

        // PurchaseOrder → Vendor (Restrict; vendor deletion blocked while POs exist)
        modelBuilder.Entity<PurchaseOrder>()
            .HasOne(po => po.Vendor)
            .WithMany()
            .HasForeignKey(po => po.VendorId)
            .OnDelete(DeleteBehavior.Restrict);

        // PurchaseOrder → Bill (SetNull; deleting a bill doesn't remove the PO)
        modelBuilder.Entity<PurchaseOrder>()
            .HasOne(po => po.Bill)
            .WithMany()
            .HasForeignKey(po => po.BillId)
            .OnDelete(DeleteBehavior.SetNull);

        // PurchaseOrderItem → PurchaseOrder (Cascade)
        modelBuilder.Entity<PurchaseOrderItem>()
            .HasOne(poi => poi.PurchaseOrder)
            .WithMany(po => po.Items)
            .HasForeignKey(poi => poi.PurchaseOrderId)
            .OnDelete(DeleteBehavior.Cascade);

        // PurchaseOrderItem → InventoryItem (optional; SetNull so deleting an item doesn't block)
        modelBuilder.Entity<PurchaseOrderItem>()
            .HasOne(poi => poi.InventoryItem)
            .WithMany()
            .HasForeignKey(poi => poi.InventoryItemId)
            .IsRequired(false)
            .OnDelete(DeleteBehavior.SetNull);

        // InventoryTransaction → PurchaseOrder (NoAction; transactions are audit data)
        modelBuilder.Entity<InventoryTransaction>()
            .HasOne(t => t.PurchaseOrder)
            .WithMany()
            .HasForeignKey(t => t.PurchaseOrderId)
            .OnDelete(DeleteBehavior.NoAction);

        // InventoryTransaction → Job (NoAction; transactions are audit data, job deletion independent)
        modelBuilder.Entity<InventoryTransaction>()
            .HasOne(t => t.Job)
            .WithMany()
            .HasForeignKey(t => t.JobId)
            .OnDelete(DeleteBehavior.NoAction);

        // ReworkRecord → original Job (Restrict; job deletion blocked while rework exists)
        modelBuilder.Entity<ReworkRecord>()
            .HasOne(r => r.Job)
            .WithMany(j => j.ReworkRecords)
            .HasForeignKey(r => r.JobId)
            .OnDelete(DeleteBehavior.Restrict);

        // ReworkRecord → optional JobItem (NoAction; item deletion doesn't cascade)
        modelBuilder.Entity<ReworkRecord>()
            .HasOne(r => r.JobItem)
            .WithMany()
            .HasForeignKey(r => r.JobItemId)
            .OnDelete(DeleteBehavior.NoAction);

        // ReworkRecord → optional rework Job (NoAction; rework job can exist independently)
        modelBuilder.Entity<ReworkRecord>()
            .HasOne(r => r.ReworkJob)
            .WithMany()
            .HasForeignKey(r => r.ReworkJobId)
            .OnDelete(DeleteBehavior.NoAction);

        // Job.OriginalJobId → Job (self-referential; NoAction)
        modelBuilder.Entity<Job>()
            .HasOne(j => j.OriginalJob)
            .WithMany()
            .HasForeignKey(j => j.OriginalJobId)
            .OnDelete(DeleteBehavior.NoAction);

        // Refund → Invoice
        modelBuilder.Entity<Refund>()
            .HasOne(r => r.Invoice)
            .WithMany(i => i.Refunds)
            .HasForeignKey(r => r.InvoiceId)
            .OnDelete(DeleteBehavior.Restrict);

        // Refund → Payment (optional)
        modelBuilder.Entity<Refund>()
            .HasOne(r => r.Payment)
            .WithMany()
            .HasForeignKey(r => r.PaymentId)
            .OnDelete(DeleteBehavior.NoAction);

        // CreditMemo → Customer
        modelBuilder.Entity<CreditMemo>()
            .HasOne(c => c.Customer)
            .WithMany()
            .HasForeignKey(c => c.CustomerId)
            .OnDelete(DeleteBehavior.Restrict);

        // CreditMemo → original Invoice (optional)
        modelBuilder.Entity<CreditMemo>()
            .HasOne(c => c.OriginalInvoice)
            .WithMany()
            .HasForeignKey(c => c.OriginalInvoiceId)
            .OnDelete(DeleteBehavior.NoAction);

        // CreditMemo → ReworkRecord (optional)
        modelBuilder.Entity<CreditMemo>()
            .HasOne(c => c.ReworkRecord)
            .WithMany()
            .HasForeignKey(c => c.ReworkRecordId)
            .OnDelete(DeleteBehavior.NoAction);

        // CreditMemoApplication → CreditMemo
        modelBuilder.Entity<CreditMemoApplication>()
            .HasOne(a => a.CreditMemo)
            .WithMany(c => c.Applications)
            .HasForeignKey(a => a.CreditMemoId)
            .OnDelete(DeleteBehavior.Restrict);

        // CreditMemoApplication → Invoice
        modelBuilder.Entity<CreditMemoApplication>()
            .HasOne(a => a.Invoice)
            .WithMany(i => i.CreditApplications)
            .HasForeignKey(a => a.InvoiceId)
            .OnDelete(DeleteBehavior.Restrict);

        // CreditMemo number unique per company
        modelBuilder.Entity<CreditMemo>()
            .HasIndex(c => new { c.CompanyId, c.MemoNumber })
            .IsUnique()
            .HasDatabaseName("IX_CreditMemos_CompanyId_MemoNumber");

        // FormulaLibraryItem — platform-level, no tenant filter, no soft delete
        // Self-referential "Inspired by" FK uses NoAction; cascade nullification handled in service.
        modelBuilder.Entity<FormulaLibraryItem>()
            .HasOne(f => f.InspiredBy)
            .WithMany()
            .HasForeignKey(f => f.InspiredByFormulaLibraryItemId)
            .IsRequired(false)
            .OnDelete(DeleteBehavior.NoAction);

        modelBuilder.Entity<FormulaLibraryItem>()
            .HasIndex(f => f.SourceCompanyId)
            .HasDatabaseName("IX_FormulaLibraryItems_SourceCompanyId");

        modelBuilder.Entity<FormulaLibraryItem>()
            .HasIndex(f => f.IsPublished)
            .HasDatabaseName("IX_FormulaLibraryItems_IsPublished");

        // FormulaLibraryImport — tenant-scoped; unique per (CompanyId, FormulaLibraryItemId)
        modelBuilder.Entity<FormulaLibraryImport>()
            .HasOne(i => i.FormulaLibraryItem)
            .WithMany()
            .HasForeignKey(i => i.FormulaLibraryItemId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<FormulaLibraryImport>()
            .HasOne(i => i.ResultingCustomItemTemplate)
            .WithMany()
            .HasForeignKey(i => i.ResultingCustomItemTemplateId)
            .OnDelete(DeleteBehavior.Restrict);

        modelBuilder.Entity<FormulaLibraryImport>()
            .HasIndex(i => new { i.CompanyId, i.FormulaLibraryItemId })
            .IsUnique()
            .HasDatabaseName("IX_FormulaLibraryImports_Company_Item");

        // FormulaLibraryRating — platform-level; one vote per company per formula
        modelBuilder.Entity<FormulaLibraryRating>()
            .HasOne(r => r.FormulaLibraryItem)
            .WithMany()
            .HasForeignKey(r => r.FormulaLibraryItemId)
            .OnDelete(DeleteBehavior.Cascade);

        modelBuilder.Entity<FormulaLibraryRating>()
            .HasIndex(r => new { r.FormulaLibraryItemId, r.CompanyId })
            .IsUnique()
            .HasDatabaseName("IX_FormulaLibraryRatings_Item_Company");

        // CustomItemTemplate → FormulaLibraryItem (nullable; only set on imported templates)
        modelBuilder.Entity<CustomItemTemplate>()
            .HasOne(t => t.SourceFormulaLibraryItem)
            .WithMany()
            .HasForeignKey(t => t.SourceFormulaLibraryItemId)
            .IsRequired(false)
            .OnDelete(DeleteBehavior.SetNull);
    }

    /// <summary>
    /// Seeds static reference data baked directly into the migration history via
    /// <see cref="ModelBuilder.Entity{TEntity}().HasData"/>.
    /// <para>
    /// Only truly global, immutable reference rows belong here (e.g., the three default
    /// <see cref="PricingTier"/> records that every new tenant starts with).
    /// Tenant-specific seed data (demo jobs, customers, etc.) is NOT seeded here —
    /// it is triggered manually through Platform Management → Seed Data so that it
    /// only runs for companies that opt in.
    /// </para>
    /// <para>
    /// Note: <c>HasData</c> rows use hard-coded IDs and are owned by the migration system.
    /// Never delete or renumber these rows without creating a corresponding data migration.
    /// </para>
    /// </summary>
    private void SeedInitialData(ModelBuilder modelBuilder)
    {
        // Seed default pricing tiers
        modelBuilder.Entity<PricingTier>().HasData(
            new PricingTier
            {
                Id = 1,
                TierName = "Standard",
                Description = "Standard pricing for regular customers",
                DiscountPercent = 0,
                IsActive = true,
                CreatedAt = DateTime.UtcNow
            },
            new PricingTier
            {
                Id = 2,
                TierName = "Preferred",
                Description = "5% discount for preferred customers",
                DiscountPercent = 5,
                IsActive = true,
                CreatedAt = DateTime.UtcNow
            },
            new PricingTier
            {
                Id = 3,
                TierName = "Premium",
                Description = "10% discount for premium customers",
                DiscountPercent = 10,
                IsActive = true,
                CreatedAt = DateTime.UtcNow
            }
        );
    }

    /// <summary>
    /// Intercepts all saves to stamp audit fields and auto-assign <c>CompanyId</c> before
    /// delegating to the EF Core base implementation.
    /// Calling <see cref="UpdateTimestampsAndTenancy"/> first ensures that no caller needs to
    /// manually set <c>CreatedAt</c>, <c>UpdatedAt</c>, <c>CreatedBy</c>, or <c>CompanyId</c>.
    /// </summary>
    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        UpdateTimestampsAndTenancy();
        return base.SaveChangesAsync(cancellationToken);
    }

    /// <summary>
    /// Iterates all Added/Modified <see cref="BaseEntity"/> entries in the change tracker and
    /// stamps audit fields and multi-tenancy data automatically.
    /// <para>
    /// For <b>Added</b> entities:
    /// <list type="bullet">
    ///   <item><description>Sets <c>CreatedAt</c> to UTC now and <c>CreatedBy</c> to the current user's Identity name.</description></item>
    ///   <item><description>If <c>CompanyId</c> is still 0 (default), auto-assigns the current tenant's company ID from <see cref="ITenantContext"/>. Controllers can override this by setting <c>CompanyId</c> explicitly before saving — useful for SuperAdmin cross-tenant operations.</description></item>
    ///   <item><description>Generates a unique <c>UnsubscribeToken</c> for new <see cref="Customer"/> records if one was not already provided.</description></item>
    /// </list>
    /// </para>
    /// <para>
    /// For <b>Modified</b> entities:
    /// <list type="bullet">
    ///   <item><description>Sets <c>UpdatedAt</c> to UTC now and <c>UpdatedBy</c> to the current user's Identity name.</description></item>
    /// </list>
    /// </para>
    /// <para>
    /// Using <see cref="ITenantContext"/> (resolved via the service provider) rather than reading
    /// the HTTP claim directly avoids a circular dependency between the DbContext and Identity,
    /// and allows the same logic to work in background jobs where <c>IHttpContextAccessor</c>
    /// returns null.
    /// </para>
    /// </summary>
    private void UpdateTimestampsAndTenancy()
    {
        var tenantContext = _serviceProvider?.GetService(typeof(ITenantContext)) as ITenantContext;
        var currentCompanyId = tenantContext?.GetCurrentCompanyId();
        var currentUser = _httpContextAccessor?.HttpContext?.User?.Identity?.Name;

        var entries = ChangeTracker.Entries()
            .Where(e => e.Entity is BaseEntity && (
                e.State == EntityState.Added ||
                e.State == EntityState.Modified));

        foreach (var entry in entries)
        {
            var entity = (BaseEntity)entry.Entity;

            if (entry.State == EntityState.Added)
            {
                // Only stamp if not already set — seeders set historical dates and rely on them being preserved.
                if (entity.CreatedAt == default)
                    entity.CreatedAt = DateTime.UtcNow;
                entity.CreatedBy = currentUser;

                // Auto-set CompanyId for new entities (if not already set)
                if (currentCompanyId.HasValue && entity.CompanyId == 0)
                {
                    entity.CompanyId = currentCompanyId.Value;
                }

                // Ensure Customer always has an unsubscribe token
                if (entity is Customer customer && string.IsNullOrEmpty(customer.UnsubscribeToken))
                {
                    customer.UnsubscribeToken = Guid.NewGuid().ToString("N");
                }
            }
            else if (entry.State == EntityState.Modified)
            {
                entity.UpdatedAt = DateTime.UtcNow;
                entity.UpdatedBy = currentUser;
            }
        }
    }
}
