/**
 * Preservation Property Tests: Compliance Remediation Display Fix
 *
 * Spec: .kiro/specs/compliance-remediation-display-fix/ (bugfix)
 *
 * These tests verify that groupByHostname() correctly aggregates existing fields
 * on UNFIXED code. They should PASS on unfixed code — they capture baseline
 * behaviour that must be preserved through the fix.
 *
 * Properties tested:
 *   P2.A — Each device.hostname appears exactly once in output
 *   P2.B — device.failing_metrics contains no duplicate metric_ids
 *   P2.C — device.seen_count >= every row's seen_count for that hostname
 *   P2.D — device.first_seen <= every row's first_seen for that hostname
 *   P2.E — device.last_seen >= every row's last_seen for that hostname
 *   P2.F — device.has_notes matches noteHostnames membership
 *
 * **Validates: Requirements 3.3, 3.5**
 */

const fc = require('fast-check');

// Mock dependencies required by the compliance module
jest.mock('../middleware/auth', () => ({
    requireTeam: () => (req, res, next) => { req.teamScope = null; next(); },
    requireAuth: () => (req, res, next) => next(),
    requireGroup: () => (req, res, next) => next(),
}));
jest.mock('../helpers/auditLog', () => jest.fn());
jest.mock('../db', () => ({
    query: jest.fn(() => Promise.resolve({ rows: [], rowCount: 0 })),
    connect: jest.fn(() => Promise.resolve({ query: jest.fn(), release: jest.fn() })),
}));

const { groupByHostname } = require('../routes/compliance');

// --- Generators ---

/**
 * Generate a valid hostname string (alphanumeric + hyphens, 1-20 chars).
 */
const hostnameArb = fc.stringMatching(/^[A-Z][A-Z0-9\-]{0,14}$/);

/**
 * Generate a metric_id string like "7.1.1", "7.2.3", etc.
 */
const metricIdArb = fc.tuple(
    fc.integer({ min: 1, max: 9 }),
    fc.integer({ min: 1, max: 9 }),
    fc.integer({ min: 1, max: 9 })
).map(([a, b, c]) => `${a}.${b}.${c}`);

/**
 * Generate a date string in YYYY-MM-DD format for first_seen/last_seen.
 */
const dateArb = fc.tuple(
    fc.integer({ min: 2023, max: 2025 }),
    fc.integer({ min: 1, max: 12 }),
    fc.integer({ min: 1, max: 28 })
).map(([y, m, d]) => `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`);

/**
 * Generate a compliance row with resolution_date = null and remediation_plan = null
 * (non-bug-condition inputs — these are the rows that should work correctly on unfixed code).
 */
function complianceRowArb(hostname, metricId) {
    return fc.record({
        hostname: fc.constant(hostname),
        ip_address: fc.constantFrom('10.0.0.1', '10.0.0.2', '192.168.1.1', ''),
        device_type: fc.constantFrom('Switch', 'Router', 'Firewall', ''),
        team: fc.constantFrom('STEAM', 'ACCESS-ENG'),
        status: fc.constantFrom('active', 'resolved'),
        metric_id: fc.constant(metricId),
        metric_desc: fc.constantFrom('Password Complexity', 'Firmware Version', 'Logging Enabled'),
        category: fc.constantFrom('Configuration', 'Patching', 'Monitoring'),
        seen_count: fc.integer({ min: 1, max: 20 }),
        first_seen: dateArb,
        last_seen: dateArb,
        resolved_on: fc.constant(null),
        resolution_date: fc.constant(null),
        remediation_plan: fc.constant(null),
    });
}

/**
 * Generate an array of compliance rows with varying hostnames and metric_ids.
 * Ensures at least 1 row, with 1-4 hostnames and 1-5 metrics per hostname.
 */
const complianceRowsArb = fc.tuple(
    fc.array(hostnameArb, { minLength: 1, maxLength: 4 }),
    fc.array(metricIdArb, { minLength: 1, maxLength: 5 })
).chain(([hostnames, metricIds]) => {
    // Ensure unique hostnames and metric_ids
    const uniqueHostnames = [...new Set(hostnames)];
    const uniqueMetricIds = [...new Set(metricIds)];

    if (uniqueHostnames.length === 0 || uniqueMetricIds.length === 0) {
        // Fallback: generate at least one row
        return complianceRowArb('HOST-A', '1.1.1').map(row => [row]);
    }

    // Generate rows: each hostname gets some subset of metric_ids
    // Some hostnames may share metric_ids (same metric failing on different devices)
    const rowArbs = [];
    for (const hostname of uniqueHostnames) {
        // Each hostname gets 1 to all metric_ids
        const metricsForHost = uniqueMetricIds.slice(0, Math.max(1, Math.ceil(Math.random() * uniqueMetricIds.length)));
        for (const metricId of metricsForHost) {
            rowArbs.push(complianceRowArb(hostname, metricId));
        }
    }

    return fc.tuple(...rowArbs).map(rows => rows);
});

/**
 * Better generator: explicitly controls the structure to ensure good coverage.
 * Generates 1-3 hostnames, each with 1-4 rows (possibly duplicate metric_ids to test dedup).
 */
const structuredRowsArb = fc.record({
    numHostnames: fc.integer({ min: 1, max: 3 }),
    numMetricsPerHost: fc.integer({ min: 1, max: 4 }),
    allowDuplicateMetrics: fc.boolean(),
}).chain(({ numHostnames, numMetricsPerHost, allowDuplicateMetrics }) => {
    const hostnameArbs = fc.array(hostnameArb, { minLength: numHostnames, maxLength: numHostnames });
    const metricArbs = fc.array(metricIdArb, { minLength: numMetricsPerHost, maxLength: numMetricsPerHost });

    return fc.tuple(hostnameArbs, metricArbs).chain(([hostnames, metrics]) => {
        const uniqueHostnames = [...new Set(hostnames)];
        if (uniqueHostnames.length === 0) return fc.constant([]);

        const rowArbs = [];
        for (const hostname of uniqueHostnames) {
            const metricsToUse = allowDuplicateMetrics
                ? metrics  // May have duplicates
                : [...new Set(metrics)];

            for (const metricId of metricsToUse) {
                rowArbs.push(complianceRowArb(hostname, metricId));
            }
            // Add an extra duplicate row for the first metric to test dedup
            if (allowDuplicateMetrics && metricsToUse.length > 0) {
                rowArbs.push(complianceRowArb(hostname, metricsToUse[0]));
            }
        }

        if (rowArbs.length === 0) return fc.constant([]);
        return fc.tuple(...rowArbs).map(rows => rows);
    });
});


// =============================================================================
// Property P2.A — Each device.hostname appears exactly once in output
// =============================================================================
//
// **Validates: Requirements 3.3, 3.5**
//
describe('Property P2.A — Each device.hostname appears exactly once in output', () => {
    it('P2.A — groupByHostname produces one device per unique hostname', () => {
        fc.assert(
            fc.property(structuredRowsArb, (rows) => {
                if (rows.length === 0) return;

                const noteHostnames = new Set();
                const devices = groupByHostname(rows, noteHostnames);

                // Each hostname in the output should appear exactly once
                const outputHostnames = devices.map(d => d.hostname);
                const uniqueOutputHostnames = new Set(outputHostnames);
                expect(outputHostnames.length).toBe(uniqueOutputHostnames.size);

                // Every hostname from input should appear in output
                const inputHostnames = new Set(rows.map(r => r.hostname));
                for (const hostname of inputHostnames) {
                    expect(uniqueOutputHostnames.has(hostname)).toBe(true);
                }
            }),
            { numRuns: 100 },
        );
    });
});


// =============================================================================
// Property P2.B — device.failing_metrics contains no duplicate metric_ids
// =============================================================================
//
// **Validates: Requirements 3.3, 3.5**
//
describe('Property P2.B — device.failing_metrics contains no duplicate metric_ids', () => {
    it('P2.B — groupByHostname deduplicates metrics by metric_id', () => {
        fc.assert(
            fc.property(structuredRowsArb, (rows) => {
                if (rows.length === 0) return;

                const noteHostnames = new Set();
                const devices = groupByHostname(rows, noteHostnames);

                for (const device of devices) {
                    const metricIds = device.failing_metrics.map(m => m.metric_id);
                    const uniqueMetricIds = new Set(metricIds);
                    expect(metricIds.length).toBe(uniqueMetricIds.size);
                }
            }),
            { numRuns: 100 },
        );
    });
});


// =============================================================================
// Property P2.C — device.seen_count >= every row's seen_count for that hostname
// =============================================================================
//
// **Validates: Requirements 3.3, 3.5**
//
describe('Property P2.C — device.seen_count >= every row seen_count for that hostname', () => {
    it('P2.C — groupByHostname picks the maximum seen_count across rows', () => {
        fc.assert(
            fc.property(structuredRowsArb, (rows) => {
                if (rows.length === 0) return;

                const noteHostnames = new Set();
                const devices = groupByHostname(rows, noteHostnames);

                for (const device of devices) {
                    const rowsForHost = rows.filter(r => r.hostname === device.hostname);
                    for (const row of rowsForHost) {
                        expect(device.seen_count).toBeGreaterThanOrEqual(row.seen_count);
                    }
                }
            }),
            { numRuns: 100 },
        );
    });
});


// =============================================================================
// Property P2.D — device.first_seen <= every row's first_seen for that hostname
// =============================================================================
//
// **Validates: Requirements 3.3, 3.5**
//
describe('Property P2.D — device.first_seen <= every row first_seen for that hostname', () => {
    it('P2.D — groupByHostname picks the earliest first_seen across rows', () => {
        fc.assert(
            fc.property(structuredRowsArb, (rows) => {
                if (rows.length === 0) return;

                const noteHostnames = new Set();
                const devices = groupByHostname(rows, noteHostnames);

                for (const device of devices) {
                    const rowsForHost = rows.filter(r => r.hostname === device.hostname);
                    for (const row of rowsForHost) {
                        if (row.first_seen && device.first_seen) {
                            expect(device.first_seen <= row.first_seen).toBe(true);
                        }
                    }
                }
            }),
            { numRuns: 100 },
        );
    });
});


// =============================================================================
// Property P2.E — device.last_seen >= every row's last_seen for that hostname
// =============================================================================
//
// **Validates: Requirements 3.3, 3.5**
//
describe('Property P2.E — device.last_seen >= every row last_seen for that hostname', () => {
    it('P2.E — groupByHostname picks the latest last_seen across rows', () => {
        fc.assert(
            fc.property(structuredRowsArb, (rows) => {
                if (rows.length === 0) return;

                const noteHostnames = new Set();
                const devices = groupByHostname(rows, noteHostnames);

                for (const device of devices) {
                    const rowsForHost = rows.filter(r => r.hostname === device.hostname);
                    for (const row of rowsForHost) {
                        if (row.last_seen && device.last_seen) {
                            expect(device.last_seen >= row.last_seen).toBe(true);
                        }
                    }
                }
            }),
            { numRuns: 100 },
        );
    });
});


// =============================================================================
// Property P2.F — device.has_notes matches noteHostnames membership
// =============================================================================
//
// **Validates: Requirements 3.3, 3.5**
//
describe('Property P2.F — device.has_notes matches noteHostnames membership', () => {
    it('P2.F — groupByHostname sets has_notes based on noteHostnames Set', () => {
        fc.assert(
            fc.property(
                structuredRowsArb,
                fc.array(hostnameArb, { minLength: 0, maxLength: 3 }),
                (rows, noteHosts) => {
                    if (rows.length === 0) return;

                    // Build noteHostnames set — include some from input, some random
                    const inputHostnames = [...new Set(rows.map(r => r.hostname))];
                    const noteHostnames = new Set([
                        ...noteHosts,
                        // Include some actual hostnames from input to test true case
                        ...inputHostnames.slice(0, Math.ceil(inputHostnames.length / 2)),
                    ]);

                    const devices = groupByHostname(rows, noteHostnames);

                    for (const device of devices) {
                        const expected = noteHostnames.has(device.hostname);
                        expect(device.has_notes).toBe(expected);
                    }
                },
            ),
            { numRuns: 100 },
        );
    });
});
