/**
 * Property-Based Test: Grouping Correctness
 *
 * Feature: queue-collapsible-sections, Property 1: Grouping Correctness
 * **Validates: Requirements 1.1, 1.2, 1.5**
 *
 * For any array of visible queue items, every item with workflow_type CARD,
 * GRANITE, or DECOM appears in the Inventory section; every FP/Archer item
 * appears in the vendor section matching its vendor field (or "Unknown" if
 * null/empty); no item appears in more than one section; and total items
 * across all sections equals input length.
 */
import fc from 'fast-check';
import { groupQueueItems } from '../utils/queueGrouping';

// ---------------------------------------------------------------------------
// Arbitraries
// ---------------------------------------------------------------------------

const INVENTORY_TYPES = ['CARD', 'GRANITE', 'DECOM'];
const VENDOR_TYPES = ['FP', 'Archer'];
const ALL_WORKFLOW_TYPES = [...INVENTORY_TYPES, ...VENDOR_TYPES];

// Generate vendor strings including edge cases: null, undefined, empty, whitespace-only, normal strings
const vendorArbitrary = fc.oneof(
  fc.constant(null),
  fc.constant(undefined),
  fc.constant(''),
  fc.constant('   '),
  fc.stringMatching(/^[A-Za-z][A-Za-z0-9 ]{0,19}$/)
);

// Generate a single queue item with a random workflow_type and vendor
const queueItemArbitrary = fc.record({
  id: fc.integer({ min: 1, max: 100000 }),
  workflow_type: fc.constantFrom(...ALL_WORKFLOW_TYPES),
  vendor: vendorArbitrary,
  status: fc.constant('pending'),
  hostname: fc.string({ minLength: 1, maxLength: 20 }),
});

// Generate arrays of queue items (0 to 50 items)
const queueItemsArbitrary = fc.array(queueItemArbitrary, { minLength: 0, maxLength: 50 });

// ---------------------------------------------------------------------------
// Property Test
// ---------------------------------------------------------------------------

describe('Queue Grouping — Property 1: Grouping Correctness', () => {
  it('every CARD/GRANITE/DECOM item appears in Inventory section', () => {
    fc.assert(
      fc.property(queueItemsArbitrary, (items) => {
        const sections = groupQueueItems(items);
        const inventorySection = sections.find(s => s.key === 'inventory');
        const inventoryItems = inventorySection ? inventorySection.items : [];

        const expectedInventoryItems = items.filter(item =>
          INVENTORY_TYPES.includes(item.workflow_type)
        );

        // Every inventory-type item must be in the inventory section
        for (const item of expectedInventoryItems) {
          expect(inventoryItems).toContain(item);
        }

        // Inventory section should contain exactly the inventory-type items
        expect(inventoryItems.length).toBe(expectedInventoryItems.length);
      }),
      { numRuns: 200 }
    );
  });

  it('every FP/Archer item appears in a vendor section matching its vendor field (or "Unknown")', () => {
    fc.assert(
      fc.property(queueItemsArbitrary, (items) => {
        const sections = groupQueueItems(items);

        const vendorSections = sections.filter(s => s.type === 'vendor');
        const allVendorItems = vendorSections.flatMap(s => s.items);

        const expectedVendorItems = items.filter(item =>
          VENDOR_TYPES.includes(item.workflow_type)
        );

        // Every vendor-type item must appear in a vendor section
        for (const item of expectedVendorItems) {
          expect(allVendorItems).toContain(item);

          // Determine expected vendor key
          const expectedVendor = item.vendor?.trim() || 'Unknown';
          const expectedKey = `vendor:${expectedVendor}`;
          const matchingSection = sections.find(s => s.key === expectedKey);

          expect(matchingSection).toBeDefined();
          expect(matchingSection.items).toContain(item);
        }

        // Vendor sections should contain exactly the vendor-type items
        expect(allVendorItems.length).toBe(expectedVendorItems.length);
      }),
      { numRuns: 200 }
    );
  });

  it('no item appears in more than one section', () => {
    fc.assert(
      fc.property(queueItemsArbitrary, (items) => {
        const sections = groupQueueItems(items);

        // Collect all items across all sections
        const allSectionItems = sections.flatMap(s => s.items);

        // Use a Set to check for duplicates (by reference)
        const seen = new Set();
        for (const item of allSectionItems) {
          expect(seen.has(item)).toBe(false);
          seen.add(item);
        }
      }),
      { numRuns: 200 }
    );
  });

  it('total items across all sections equals input length', () => {
    fc.assert(
      fc.property(queueItemsArbitrary, (items) => {
        const sections = groupQueueItems(items);
        const totalInSections = sections.reduce((sum, s) => sum + s.items.length, 0);

        expect(totalInSections).toBe(items.length);
      }),
      { numRuns: 200 }
    );
  });
});
