/**
 * Property-Based Test: Non-metric category derivation is the set difference with accurate counts
 *
 * Feature: compliance-nonmetric-filters, Property 1: Non-metric category derivation
 * **Validates: Requirements 1.1, 1.2, 2.2, 2.4**
 *
 * For any set of devices and any set of summary entries for a team,
 * deriveNonMetricCategories returns exactly the metric_ids present in at least
 * one device's failing_metrics array that do not appear in any summary entry's
 * metric_id — deduplicated, sorted alphabetically by metricId, and each with a
 * count equal to the number of devices whose failing_metrics contains that metric_id.
 */
import fc from 'fast-check';
import { deriveNonMetricCategories } from '../components/pages/CompliancePage';

// Generators
const metricIdArb = fc.stringMatching(/^[A-Za-z0-9_.]{1,20}$/);

const failingMetricArb = fc.record({
    metric_id: metricIdArb,
    metric_desc: fc.constant(''),
    category: fc.constant(''),
});

const deviceArb = fc.record({
    hostname: fc.string({ minLength: 1, maxLength: 30 }),
    failing_metrics: fc.array(failingMetricArb, { minLength: 0, maxLength: 8 }),
});

const summaryEntryArb = fc.record({
    metric_id: metricIdArb,
    team: fc.constant('STEAM'),
    compliance_pct: fc.double({ min: 0, max: 1 }),
    status: fc.constantFrom('Meets/Exceeds Target', 'Within 15% of Target', 'Below 15% of Target'),
});

const categoriesConfigArb = fc.dictionary(metricIdArb, fc.constantFrom(
    'Vulnerability Management', 'Access & MFA', 'Logging & Monitoring',
    'Asset Data Quality', 'Endpoint Protection', 'Application Security',
    'Disaster Recovery', 'Decommissioned Assets'
));

describe('Compliance Non-Metric Derivation — Property 1: Set difference with accurate counts', () => {
    it('returned metric_ids are exactly the set difference of device metric_ids minus summary metric_ids', () => {
        fc.assert(
            fc.property(
                fc.array(deviceArb, { minLength: 0, maxLength: 20 }),
                fc.array(summaryEntryArb, { minLength: 0, maxLength: 10 }),
                categoriesConfigArb,
                (devices, summaryEntries, config) => {
                    const result = deriveNonMetricCategories(devices, summaryEntries, config);

                    const summaryIds = new Set(summaryEntries.map(e => e.metric_id));
                    const deviceMetricIds = new Set();
                    for (const d of devices) {
                        for (const m of (d.failing_metrics || [])) {
                            if (m.metric_id) deviceMetricIds.add(m.metric_id);
                        }
                    }
                    const expectedIds = new Set([...deviceMetricIds].filter(id => !summaryIds.has(id)));

                    const resultIds = new Set(result.map(r => r.metricId));
                    expect(resultIds).toEqual(expectedIds);
                }
            ),
            { numRuns: 100 }
        );
    });

    it('returned list is deduplicated and sorted alphabetically by metricId', () => {
        fc.assert(
            fc.property(
                fc.array(deviceArb, { minLength: 0, maxLength: 20 }),
                fc.array(summaryEntryArb, { minLength: 0, maxLength: 10 }),
                categoriesConfigArb,
                (devices, summaryEntries, config) => {
                    const result = deriveNonMetricCategories(devices, summaryEntries, config);

                    // Deduplicated
                    const ids = result.map(r => r.metricId);
                    expect(ids.length).toBe(new Set(ids).size);

                    // Sorted alphabetically
                    const sorted = [...ids].sort((a, b) => a.localeCompare(b));
                    expect(ids).toEqual(sorted);
                }
            ),
            { numRuns: 100 }
        );
    });

    it('each count equals the number of devices whose failing_metrics contains that metric_id', () => {
        fc.assert(
            fc.property(
                fc.array(deviceArb, { minLength: 0, maxLength: 20 }),
                fc.array(summaryEntryArb, { minLength: 0, maxLength: 10 }),
                categoriesConfigArb,
                (devices, summaryEntries, config) => {
                    const result = deriveNonMetricCategories(devices, summaryEntries, config);

                    for (const { metricId, count } of result) {
                        const expectedCount = devices.filter(d =>
                            (d.failing_metrics || []).some(m => m.metric_id === metricId)
                        ).length;
                        expect(count).toBe(expectedCount);
                    }
                }
            ),
            { numRuns: 100 }
        );
    });
});
