/**
 * Preservation Property Tests: Compliance Duplicate Failing Metrics
 *
 * Spec: .kiro/specs/compliance-duplicate-failing-metrics/ (bugfix)
 *
 * These tests verify that UNIQUE-KEY inputs (where every (hostname, metric_id)
 * is unique across verticals) produce unchanged outputs after the fix.
 * They should PASS on unfixed code — they capture baseline behaviour to preserve.
 *
 * Properties tested:
 *   7.A — /items unique-key preservation (response equality across team/status combos)
 *   7.B — /items/:hostname unique-key preservation (active-then-resolved ordering)
 *   7.C — /vcl/stats unique-key preservation (stats, donut, heavy_hitters, vertical_breakdown)
 *   7.D — /mttr unique-key preservation (aging array equality)
 *   7.E — persistUpload() unique-key preservation (snapshot rows for single-status-per-hostname)
 *   7.F — /items query-param validation (HTTP 400 for invalid team/status, 404 for unknown hostname)
 *   8.A — Representative-row policy on duplicates (SKIP — asserts post-fix behavior)
 *
 * **Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7**
 */

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

// --- Mocks (must be installed BEFORE requiring the route module) ---

jest.mock('../middleware/auth', () => ({
    requireAuth: () => (req, res, next) => {
        req.user = { id: 1, username: 'testuser', group: 'Admin' };
        next();
    },
    requireGroup: () => (req, res, next) => next(),
}));

jest.mock('../helpers/auditLog', () => jest.fn());

// Programmable pg pool mock
let queryHandler = () => Promise.resolve({ rows: [], rowCount: 0 });
const recordedQueries = [];

const mockPool = {
    query: jest.fn((text, params) => {
        recordedQueries.push({ text, params, on: 'pool' });
        return queryHandler(text, params);
    }),
    connect: jest.fn(() => Promise.resolve({
        query: jest.fn((text, params) => {
            recordedQueries.push({ text, params, on: 'client' });
            return queryHandler(text, params);
        }),
        release: jest.fn(),
    })),
};
jest.mock('../db', () => mockPool);

const { createComplianceRouter, persistUpload } = require('../routes/compliance');

// --- HTTP helper ---

function request(server, method, urlPath) {
    return new Promise((resolve, reject) => {
        const addr = server.address();
        const options = {
            hostname: '127.0.0.1',
            port: addr.port,
            path: urlPath,
            method,
            headers: { 'Content-Type': 'application/json' },
        };
        const req = http.request(options, (res) => {
            const chunks = [];
            res.on('data', (chunk) => chunks.push(chunk));
            res.on('end', () => {
                const raw = Buffer.concat(chunks).toString();
                let json;
                try { json = JSON.parse(raw); } catch { json = null; }
                resolve({ statusCode: res.statusCode, body: json });
            });
        });
        req.on('error', reject);
        req.end();
    });
}

// --- Query handler builder ---

/**
 * Build a query handler from an ordered list of routes. Each route's `match`
 * is a substring or RegExp tested against the SQL text. The first match wins.
 */
function makeQueryHandler(routes) {
    return (text, params) => {
        for (const route of routes) {
            const target = route.match;
            const hit = target instanceof RegExp ? target.test(text) : text.includes(target);
            if (hit) {
                const rows = typeof route.rows === 'function'
                    ? (route.rows(text, params) || [])
                    : (route.rows || []);
                return Promise.resolve({ rows, rowCount: rows.length });
            }
        }
        return Promise.resolve({ rows: [], rowCount: 0 });
    };
}


// --- Unique-key fixture builders ---
// Every (hostname, metric_id) pair is unique across the array.
// These represent the "no bug condition" case — behaviour must be unchanged after the fix.

const ALLOWED_TEAMS = ['STEAM', 'ACCESS-ENG', 'ACCESS-OPS', 'INTELDEV'];

/**
 * fixtureUniqueKeyActive — Multiple devices, each with unique metric_ids, all active.
 * Covers the standard /items and /mttr paths.
 */
function fixtureUniqueKeyActive() {
    return [
        {
            id: 1, hostname: 'DEVICE-A', ip_address: '10.0.0.1',
            device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1',
            metric_desc: 'Password Complexity', category: 'Configuration',
            status: 'active', seen_count: 3, vertical: null,
            upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null,
            resolution_date: null, remediation_plan: null, extra_json: '{}',
        },
        {
            id: 2, hostname: 'DEVICE-A', ip_address: '10.0.0.1',
            device_type: 'Switch', team: 'STEAM', metric_id: '7.2.1',
            metric_desc: 'Firmware Version', category: 'Patching',
            status: 'active', seen_count: 5, vertical: null,
            upload_id: 100, first_seen_upload_id: 85, resolved_upload_id: null,
            resolution_date: '2025-09-30', remediation_plan: null, extra_json: '{}',
        },
        {
            id: 3, hostname: 'DEVICE-B', ip_address: '10.0.0.2',
            device_type: 'Router', team: 'ACCESS-ENG', metric_id: '7.1.1',
            metric_desc: 'Password Complexity', category: 'Configuration',
            status: 'active', seen_count: 1, vertical: 'NTS_AEO',
            upload_id: 110, first_seen_upload_id: 110, resolved_upload_id: null,
            resolution_date: null, remediation_plan: null, extra_json: '{}',
        },
        {
            id: 4, hostname: 'DEVICE-C', ip_address: '10.0.0.3',
            device_type: 'Firewall', team: 'STEAM', metric_id: '7.3.1',
            metric_desc: 'Logging Enabled', category: 'Monitoring',
            status: 'active', seen_count: 7, vertical: null,
            upload_id: 100, first_seen_upload_id: 70, resolved_upload_id: null,
            resolution_date: '2025-08-15', remediation_plan: 'Upgrade firmware', extra_json: '{}',
        },
    ];
}

/**
 * fixtureUniqueKeyMixed — Devices with both active and resolved metrics.
 * Covers /items/:hostname ordering and /items with status=resolved.
 */
function fixtureUniqueKeyMixed() {
    return [
        {
            id: 10, hostname: 'MIXED-DEVICE', ip_address: '10.1.0.1',
            device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1',
            metric_desc: 'Password Complexity', category: 'Configuration',
            status: 'active', seen_count: 4, vertical: null,
            upload_id: 100, first_seen_upload_id: 80, resolved_upload_id: null,
            resolution_date: null, remediation_plan: null, extra_json: '{}',
        },
        {
            id: 11, hostname: 'MIXED-DEVICE', ip_address: '10.1.0.1',
            device_type: 'Switch', team: 'STEAM', metric_id: '7.2.1',
            metric_desc: 'Firmware Version', category: 'Patching',
            status: 'resolved', seen_count: 6, vertical: null,
            upload_id: 105, first_seen_upload_id: 75, resolved_upload_id: 105,
            resolution_date: null, remediation_plan: null, extra_json: '{}',
        },
        {
            id: 12, hostname: 'MIXED-DEVICE', ip_address: '10.1.0.1',
            device_type: 'Switch', team: 'STEAM', metric_id: '7.3.1',
            metric_desc: 'Logging Enabled', category: 'Monitoring',
            status: 'active', seen_count: 2, vertical: null,
            upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null,
            resolution_date: '2025-10-01', remediation_plan: null, extra_json: '{}',
        },
    ];
}

/**
 * fixtureSingleStatusPerHostname — For persistUpload() snapshot preservation.
 * Each hostname has only active OR only resolved rows (never both).
 */
function fixtureSingleStatusPerHostname() {
    return [
        {
            id: 20, hostname: 'ACTIVE-ONLY-HOST', ip_address: '10.2.0.1',
            device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1',
            metric_desc: 'Password Complexity', category: 'Configuration',
            status: 'active', seen_count: 3, vertical: null,
            upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null,
            resolution_date: null, remediation_plan: null, extra_json: '{}',
        },
        {
            id: 21, hostname: 'ACTIVE-ONLY-HOST', ip_address: '10.2.0.1',
            device_type: 'Switch', team: 'STEAM', metric_id: '7.2.1',
            metric_desc: 'Firmware Version', category: 'Patching',
            status: 'active', seen_count: 5, vertical: null,
            upload_id: 100, first_seen_upload_id: 85, resolved_upload_id: null,
            resolution_date: null, remediation_plan: null, extra_json: '{}',
        },
        {
            id: 22, hostname: 'RESOLVED-ONLY-HOST', ip_address: '10.2.0.2',
            device_type: 'Router', team: 'STEAM', metric_id: '7.3.1',
            metric_desc: 'Logging Enabled', category: 'Monitoring',
            status: 'resolved', seen_count: 4, vertical: null,
            upload_id: 105, first_seen_upload_id: 80, resolved_upload_id: 105,
            resolution_date: null, remediation_plan: null, extra_json: '{}',
        },
    ];
}

// --- Query handler installers for preservation tests ---

/**
 * Install handler for /items unique-key preservation.
 */
function installItemsHandler(items) {
    queryHandler = makeQueryHandler([
        {
            match: /FROM\s+compliance_items\s+ci[\s\S]*WHERE\s+ci\.team\s*=\s*\$1\s+AND\s+ci\.status\s*=\s*\$2/i,
            rows: (_text, params) => {
                const [team, status] = params || [];
                return items
                    .filter(i => i.team === team && i.status === status)
                    .map(i => ({
                        hostname: i.hostname, ip_address: i.ip_address,
                        device_type: i.device_type, team: i.team,
                        metric_id: i.metric_id, metric_desc: i.metric_desc,
                        category: i.category, status: i.status,
                        seen_count: i.seen_count,
                        first_seen: '2025-01-01', last_seen: '2025-05-01',
                        resolved_on: i.resolved_upload_id ? '2025-05-01' : null,
                    }));
            },
        },
        { match: 'compliance_notes', rows: [] },
    ]);
}

/**
 * Install handler for /items/:hostname unique-key preservation.
 */
function installItemsHostnameHandler(items) {
    queryHandler = makeQueryHandler([
        {
            match: /FROM\s+compliance_items\s+ci[\s\S]*WHERE\s+ci\.hostname\s*=\s*\$1/i,
            rows: (_text, params) => {
                const [hostname] = params || [];
                return items
                    .filter(i => i.hostname === hostname)
                    .sort((a, b) => {
                        if (a.status !== b.status) return b.status.localeCompare(a.status);
                        return a.metric_id.localeCompare(b.metric_id);
                    })
                    .map(i => ({
                        metric_id: i.metric_id, metric_desc: i.metric_desc,
                        category: i.category, status: i.status,
                        ip_address: i.ip_address, device_type: i.device_type,
                        team: i.team, seen_count: i.seen_count,
                        extra_json: i.extra_json || '{}',
                        resolution_date: i.resolution_date,
                        remediation_plan: i.remediation_plan,
                        first_seen: '2025-01-01', first_seen_at: '2025-01-01T00:00:00Z',
                        last_seen: '2025-05-01', last_seen_at: '2025-05-01T00:00:00Z',
                        resolved_on: i.resolved_upload_id ? '2025-05-01' : null,
                    }));
            },
        },
        { match: 'compliance_notes', rows: [] },
        { match: 'compliance_item_history', rows: [] },
    ]);
}

/**
 * Install handler for /vcl/stats unique-key preservation.
 */
function installVclStatsHandler(items) {
    const activeItems = items.filter(i => i.status === 'active');

    queryHandler = makeQueryHandler([
        // Global stats query
        {
            match: /COUNT\(DISTINCT\s+hostname\)\s+AS\s+total_devices/i,
            rows: () => {
                const allHostnames = new Set(items.map(i => i.hostname));
                const activeHostnames = new Set(activeItems.map(i => i.hostname));
                const compliantHostnames = [...allHostnames].filter(h => !activeHostnames.has(h));
                return [{
                    total_devices: allHostnames.size,
                    in_scope: allHostnames.size,
                    compliant: compliantHostnames.length,
                    non_compliant: activeHostnames.size,
                }];
            },
        },
        // Donut query
        {
            match: /MAX\(resolution_date\)[\s\S]*GROUP\s+BY\s+hostname/i,
            rows: () => {
                const byHost = {};
                for (const i of activeItems) {
                    if (!byHost[i.hostname]) byHost[i.hostname] = { hostname: i.hostname, resolution_date: null };
                    if (i.resolution_date) byHost[i.hostname].resolution_date = i.resolution_date;
                }
                return Object.values(byHost);
            },
        },
        // Heavy-hitters query — matches the new CTE pattern: WITH device_team AS (SELECT DISTINCT ON (hostname)...)
        {
            match: /WITH\s+device_team\s+AS[\s\S]*SELECT\s+DISTINCT\s+ON\s*\(hostname\)[\s\S]*WHERE\s+status\s*=\s*'active'[\s\S]*GROUP\s+BY\s+team/i,
            rows: () => {
                const teamCounts = {};
                const teamDates = {};
                for (const i of activeItems) {
                    const t = i.team || 'Unknown';
                    if (!teamCounts[t]) { teamCounts[t] = new Set(); teamDates[t] = null; }
                    teamCounts[t].add(i.hostname);
                    if (i.resolution_date && (!teamDates[t] || i.resolution_date > teamDates[t])) {
                        teamDates[t] = i.resolution_date;
                    }
                }
                return Object.entries(teamCounts)
                    .map(([team, hosts]) => ({
                        team,
                        non_compliant: hosts.size,
                        compliance_date: teamDates[team] ? new Date(teamDates[team] + 'T00:00:00Z') : null,
                    }))
                    .sort((a, b) => b.non_compliant - a.non_compliant);
            },
        },
        // Per-team total query — matches the new CTE pattern: WITH device_team AS (...) SELECT COUNT(*)::int AS total FROM device_team WHERE team = $1
        {
            match: /WITH\s+device_team\s+AS[\s\S]*SELECT\s+COUNT\(\*\).*AS\s+total\s+FROM\s+device_team\s+WHERE\s+team/i,
            rows: (_text, params) => {
                const [team] = params || [];
                const hosts = new Set(items.filter(i => (i.team || 'Unknown') === team).map(i => i.hostname));
                return [{ total: hosts.size }];
            },
        },
        // Forecast query — matches the new DISTINCT ON pattern
        {
            match: /SELECT\s+DISTINCT\s+ON\s*\(hostname,\s*metric_id\)\s*resolution_date/i,
            rows: (_text, params) => {
                const [team] = params || [];
                return activeItems
                    .filter(i => (i.team || 'Unknown') === team && i.resolution_date != null)
                    .map(i => ({ resolution_date: i.resolution_date }));
            },
        },
        // vcl_vertical_metadata
        { match: 'vcl_vertical_metadata', rows: [] },
    ]);
}

/**
 * Install handler for /mttr unique-key preservation.
 */
function installMttrHandler(items) {
    queryHandler = makeQueryHandler([
        {
            match: /seen_count[\s\S]*FROM\s+compliance_items\s+WHERE\s+status\s*=\s*'active'/i,
            rows: () => {
                return items
                    .filter(i => i.status === 'active')
                    .map(i => ({ seen_count: i.seen_count || 1, team: i.team }));
            },
        },
    ]);
}

/**
 * Install handler for persistUpload() unique-key preservation.
 */
function installPersistUploadHandler(items) {
    queryHandler = makeQueryHandler([
        // persistUpload: SELECT active items
        {
            match: /SELECT\s+id,\s*hostname,\s*metric_id,\s*seen_count,\s*first_seen_upload_id\s+FROM\s+compliance_items\s+WHERE\s+status\s*=\s*'active'/i,
            rows: () => items.filter(i => i.status === 'active').map(i => ({
                id: i.id, hostname: i.hostname, metric_id: i.metric_id,
                seen_count: i.seen_count, first_seen_upload_id: i.first_seen_upload_id,
            })),
        },
        { match: 'BEGIN', rows: [] },
        { match: 'COMMIT', rows: [] },
        {
            match: /INSERT\s+INTO\s+compliance_uploads/i,
            rows: () => [{ id: 999 }],
        },
        { match: /UPDATE\s+compliance_uploads/i, rows: [] },
        { match: /UPDATE\s+compliance_items/i, rows: [] },
        // Snapshot query — matches the new CTE pattern: WITH hostname_status AS (...) SELECT team AS vertical, COUNT(*)::int AS total_devices...
        {
            match: /WITH\s+hostname_status\s+AS[\s\S]*SELECT\s+team\s+AS\s+vertical/i,
            rows: (_text, params) => {
                const [vertical] = params || [];
                const filtered = items.filter(i => {
                    if (vertical === null || vertical === undefined) return i.vertical == null;
                    return i.vertical === vertical;
                }).filter(i => i.team != null);

                const byTeam = {};
                for (const i of filtered) {
                    if (!byTeam[i.team]) byTeam[i.team] = { team: i.team, hostnames: {} };
                    if (!byTeam[i.team].hostnames[i.hostname]) {
                        byTeam[i.team].hostnames[i.hostname] = new Set();
                    }
                    byTeam[i.team].hostnames[i.hostname].add(i.status);
                }

                return Object.values(byTeam).map(t => {
                    const hostnames = Object.keys(t.hostnames);
                    const total_devices = hostnames.length;
                    let compliant = 0;
                    let non_compliant = 0;
                    for (const h of hostnames) {
                        const statuses = t.hostnames[h];
                        // CTE uses MIN(status): 'active' < 'resolved', so if active exists, host is non_compliant
                        if (statuses.has('active')) non_compliant++;
                        else if (statuses.has('resolved')) compliant++;
                    }
                    return {
                        vertical: vertical,
                        team: t.team,
                        total_devices,
                        compliant,
                        non_compliant,
                    };
                });
            },
        },
        { match: /INSERT\s+INTO\s+compliance_snapshots/i, rows: [] },
    ]);
}

// --- Express server setup ---

let app, server;

beforeAll((done) => {
    app = express();
    app.use(express.json());
    const mockUpload = { single: () => (req, res, next) => next() };
    app.use('/api/compliance', createComplianceRouter(mockUpload));
    server = app.listen(0, '127.0.0.1', done);
});

afterAll((done) => {
    server.close(done);
});

beforeEach(() => {
    mockPool.query.mockClear();
    mockPool.connect.mockClear();
    recordedQueries.length = 0;
    queryHandler = () => Promise.resolve({ rows: [], rowCount: 0 });
});


// =============================================================================
// Property 7.A — /items unique-key preservation
// =============================================================================
//
// For any unique-key fixture, the response from GET /items?team=...&status=...
// returns the correct devices with correct failing_metrics counts.
// Each (hostname, metric_id) is unique, so no dedup should alter the output.
//
// **Validates: Requirements 3.1, 3.4**
//
describe('Property 7.A — /items unique-key preservation', () => {
    it('7.A — unique-key active items: response contains correct devices and metric counts', async () => {
        const items = fixtureUniqueKeyActive();
        installItemsHandler(items);

        const res = await request(server, 'GET', '/api/compliance/items?team=STEAM&status=active');
        expect(res.statusCode).toBe(200);
        expect(res.body.team).toBe('STEAM');
        expect(res.body.status).toBe('active');

        // STEAM active items: DEVICE-A (7.1.1, 7.2.1) and DEVICE-C (7.3.1)
        const steamActive = items.filter(i => i.team === 'STEAM' && i.status === 'active');
        const expectedHostnames = [...new Set(steamActive.map(i => i.hostname))];
        expect(res.body.devices.length).toBe(expectedHostnames.length);

        const deviceA = res.body.devices.find(d => d.hostname === 'DEVICE-A');
        expect(deviceA).toBeDefined();
        expect(deviceA.failing_metrics.length).toBe(2);
        expect(deviceA.failing_metrics.map(m => m.metric_id).sort()).toEqual(['7.1.1', '7.2.1']);

        const deviceC = res.body.devices.find(d => d.hostname === 'DEVICE-C');
        expect(deviceC).toBeDefined();
        expect(deviceC.failing_metrics.length).toBe(1);
        expect(deviceC.failing_metrics[0].metric_id).toBe('7.3.1');
    });

    it('7.A — unique-key items across teams: each team returns only its devices', async () => {
        const items = fixtureUniqueKeyActive();
        installItemsHandler(items);

        const resEng = await request(server, 'GET', '/api/compliance/items?team=ACCESS-ENG&status=active');
        expect(resEng.statusCode).toBe(200);
        expect(resEng.body.devices.length).toBe(1);
        expect(resEng.body.devices[0].hostname).toBe('DEVICE-B');
        expect(resEng.body.devices[0].failing_metrics.length).toBe(1);
    });

    it('7.A property — for any unique-key input, failing_metrics count equals distinct metric_ids per hostname', async () => {
        await fc.assert(
            fc.asyncProperty(
                fc.constantFrom('STEAM', 'ACCESS-ENG', 'ACCESS-OPS', 'INTELDEV'),
                fc.constantFrom('active', 'resolved'),
                async (team, status) => {
                    const items = fixtureUniqueKeyActive();
                    installItemsHandler(items);

                    const res = await request(server, 'GET', `/api/compliance/items?team=${team}&status=${status}`);
                    expect(res.statusCode).toBe(200);

                    // For unique-key inputs, each device's failing_metrics count
                    // should equal the number of distinct metric_ids for that hostname
                    const filtered = items.filter(i => i.team === team && i.status === status);
                    const expectedByHost = {};
                    for (const i of filtered) {
                        if (!expectedByHost[i.hostname]) expectedByHost[i.hostname] = new Set();
                        expectedByHost[i.hostname].add(i.metric_id);
                    }

                    for (const device of res.body.devices) {
                        const expected = expectedByHost[device.hostname];
                        if (expected) {
                            expect(device.failing_metrics.length).toBe(expected.size);
                        }
                    }
                },
            ),
            { numRuns: 8 },
        );
    });
});

// =============================================================================
// Property 7.B — /items/:hostname unique-key preservation
// =============================================================================
//
// For any unique-key fixture, the response from GET /items/:hostname preserves
// active-then-resolved ordering (status DESC alphabetically means 'resolved'
// sorts before 'active' in DESC, but the route uses status DESC which puts
// 'active' items first because 'a' < 'r' reversed). Within each status group,
// metrics are sorted by metric_id.
//
// **Validates: Requirements 3.2, 3.3**
//
describe('Property 7.B — /items/:hostname unique-key preservation', () => {
    it('7.B — mixed-status device: active metrics before resolved, sorted by metric_id within group', async () => {
        const items = fixtureUniqueKeyMixed();
        installItemsHostnameHandler(items);

        const res = await request(server, 'GET', '/api/compliance/items/MIXED-DEVICE');
        expect(res.statusCode).toBe(200);
        expect(res.body.metrics).toBeDefined();

        // The route orders by status DESC, metric_id — 'resolved' > 'active' alphabetically
        // so status DESC puts 'resolved' first, then 'active'
        // Actually: ORDER BY ci.status DESC, ci.metric_id
        // 'resolved' > 'active' alphabetically, so DESC puts 'resolved' first? No:
        // DESC on strings: 'r' > 'a', so DESC means 'r' comes first.
        // Wait — the exploration test says "active-then-resolved ordering" but the SQL is
        // ORDER BY ci.status DESC which puts 'resolved' before 'active' (r > a in DESC).
        // Let's verify what the actual route produces and preserve it.

        // The fixture has: 7.1.1 active, 7.2.1 resolved, 7.3.1 active
        // SQL ORDER BY status DESC, metric_id → resolved first, then active
        // So: 7.2.1 (resolved), then 7.1.1 (active), 7.3.1 (active)
        // Wait: DESC on status means 'r' > 'a', so resolved comes first.
        // Then within resolved: 7.2.1
        // Then within active: 7.1.1, 7.3.1

        const metrics = res.body.metrics;
        expect(metrics.length).toBe(3);

        // Verify ordering: resolved first (status DESC), then active, sorted by metric_id within
        expect(metrics[0].status).toBe('resolved');
        expect(metrics[0].metric_id).toBe('7.2.1');
        expect(metrics[1].status).toBe('active');
        expect(metrics[1].metric_id).toBe('7.1.1');
        expect(metrics[2].status).toBe('active');
        expect(metrics[2].metric_id).toBe('7.3.1');
    });

    it('7.B — each metric appears exactly once per (metric_id, status) for unique-key input', async () => {
        const items = fixtureUniqueKeyMixed();
        installItemsHostnameHandler(items);

        const res = await request(server, 'GET', '/api/compliance/items/MIXED-DEVICE');
        expect(res.statusCode).toBe(200);

        const pairs = res.body.metrics.map(m => `${m.metric_id}|${m.status}`);
        expect(pairs.length).toBe(new Set(pairs).size);
    });

    it('7.B — seen_count is preserved correctly for unique-key items', async () => {
        const items = fixtureUniqueKeyMixed();
        installItemsHostnameHandler(items);

        const res = await request(server, 'GET', '/api/compliance/items/MIXED-DEVICE');
        expect(res.statusCode).toBe(200);

        // Verify seen_count matches the fixture values
        const metric711 = res.body.metrics.find(m => m.metric_id === '7.1.1');
        expect(metric711.seen_count).toBe(4);

        const metric721 = res.body.metrics.find(m => m.metric_id === '7.2.1');
        expect(metric721.seen_count).toBe(6);

        const metric731 = res.body.metrics.find(m => m.metric_id === '7.3.1');
        expect(metric731.seen_count).toBe(2);
    });

    it('7.B property — for any unique-key input, ordering is status DESC then metric_id ASC', async () => {
        await fc.assert(
            fc.asyncProperty(
                fc.constant(true),
                async () => {
                    const items = fixtureUniqueKeyMixed();
                    installItemsHostnameHandler(items);

                    const res = await request(server, 'GET', '/api/compliance/items/MIXED-DEVICE');
                    expect(res.statusCode).toBe(200);

                    const metrics = res.body.metrics;
                    // Verify ordering: status DESC (resolved before active), then metric_id ASC
                    for (let i = 1; i < metrics.length; i++) {
                        const prev = metrics[i - 1];
                        const curr = metrics[i];
                        if (prev.status === curr.status) {
                            expect(prev.metric_id <= curr.metric_id).toBe(true);
                        } else {
                            // status DESC: 'resolved' > 'active' alphabetically
                            expect(prev.status >= curr.status).toBe(true);
                        }
                    }
                },
            ),
            { numRuns: 5 },
        );
    });
});


// =============================================================================
// Property 7.C — /vcl/stats unique-key preservation
// =============================================================================
//
// For any unique-key fixture, the /vcl/stats response has consistent stats,
// donut, heavy_hitters, and vertical_breakdown values.
//
// **Validates: Requirements 3.6**
//
describe('Property 7.C — /vcl/stats unique-key preservation', () => {
    it('7.C — unique-key items: stats.non_compliant equals distinct active hostnames', async () => {
        const items = fixtureUniqueKeyActive();
        installVclStatsHandler(items);

        const res = await request(server, 'GET', '/api/compliance/vcl/stats');
        expect(res.statusCode).toBe(200);

        const activeHostnames = new Set(items.filter(i => i.status === 'active').map(i => i.hostname));
        expect(res.body.stats.non_compliant).toBe(activeHostnames.size);
    });

    it('7.C — unique-key items: SUM(heavy_hitters.non_compliant) === stats.non_compliant', async () => {
        const items = fixtureUniqueKeyActive();
        installVclStatsHandler(items);

        const res = await request(server, 'GET', '/api/compliance/vcl/stats');
        expect(res.statusCode).toBe(200);

        const sumHH = res.body.heavy_hitters.reduce((s, hh) => s + hh.non_compliant, 0);
        expect(sumHH).toBe(res.body.stats.non_compliant);
    });

    it('7.C — unique-key items: donut blocked + in_progress equals non_compliant', async () => {
        const items = fixtureUniqueKeyActive();
        installVclStatsHandler(items);

        const res = await request(server, 'GET', '/api/compliance/vcl/stats');
        expect(res.statusCode).toBe(200);

        const donutTotal = res.body.donut.blocked.count + res.body.donut.in_progress.count;
        expect(donutTotal).toBe(res.body.stats.non_compliant);
    });

    it('7.C — unique-key items: vertical_breakdown blockers are non-negative', async () => {
        const items = fixtureUniqueKeyActive();
        installVclStatsHandler(items);

        const res = await request(server, 'GET', '/api/compliance/vcl/stats');
        expect(res.statusCode).toBe(200);

        for (const vb of res.body.vertical_breakdown) {
            expect(vb.blockers).toBeGreaterThanOrEqual(0);
        }
    });

    it('7.C property — for any unique-key input, heavy_hitters sum reconciles with stats', async () => {
        await fc.assert(
            fc.asyncProperty(
                fc.integer({ min: 1, max: 5 }),
                async (numDevices) => {
                    // Generate unique-key items with varying device counts
                    const items = [];
                    for (let d = 0; d < numDevices; d++) {
                        items.push({
                            id: 100 + d, hostname: `GEN-DEVICE-${d}`, ip_address: `10.0.${d}.1`,
                            device_type: 'Switch', team: ALLOWED_TEAMS[d % ALLOWED_TEAMS.length],
                            metric_id: `7.${d + 1}.1`, metric_desc: `Metric ${d}`, category: 'Config',
                            status: 'active', seen_count: d + 1, vertical: null,
                            upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null,
                            resolution_date: d % 2 === 0 ? '2025-09-30' : null,
                            remediation_plan: null, extra_json: '{}',
                        });
                    }
                    installVclStatsHandler(items);

                    const res = await request(server, 'GET', '/api/compliance/vcl/stats');
                    expect(res.statusCode).toBe(200);

                    // Heavy hitters sum should equal stats.non_compliant
                    const sumHH = res.body.heavy_hitters.reduce((s, hh) => s + hh.non_compliant, 0);
                    expect(sumHH).toBe(res.body.stats.non_compliant);

                    // All blockers non-negative
                    for (const vb of res.body.vertical_breakdown) {
                        expect(vb.blockers).toBeGreaterThanOrEqual(0);
                    }
                },
            ),
            { numRuns: 10 },
        );
    });
});

// =============================================================================
// Property 7.D — /mttr unique-key preservation
// =============================================================================
//
// For any unique-key fixture, the /mttr aging array correctly buckets each
// active item exactly once.
//
// **Validates: Requirements 3.7**
//
describe('Property 7.D — /mttr unique-key preservation', () => {
    it('7.D — unique-key active items: SUM(aging.total) equals active item count', async () => {
        const items = fixtureUniqueKeyActive();
        installMttrHandler(items);

        const res = await request(server, 'GET', '/api/compliance/mttr');
        expect(res.statusCode).toBe(200);

        const totalBucketed = res.body.aging.reduce((s, b) => s + b.total, 0);
        const activeCount = items.filter(i => i.status === 'active').length;
        expect(totalBucketed).toBe(activeCount);
    });

    it('7.D — unique-key items: per-team bucket totals match team active counts', async () => {
        const items = fixtureUniqueKeyActive();
        installMttrHandler(items);

        const res = await request(server, 'GET', '/api/compliance/mttr');
        expect(res.statusCode).toBe(200);

        // Sum per-team across all buckets
        const teamTotals = {};
        for (const bucket of res.body.aging) {
            for (const team of ALLOWED_TEAMS) {
                if (!teamTotals[team]) teamTotals[team] = 0;
                teamTotals[team] += bucket[team] || 0;
            }
        }

        // Compare with fixture
        for (const team of ALLOWED_TEAMS) {
            const expected = items.filter(i => i.status === 'active' && i.team === team).length;
            expect(teamTotals[team]).toBe(expected);
        }
    });

    it('7.D — unique-key items: correct bucket assignment by seen_count', async () => {
        const items = fixtureUniqueKeyActive();
        installMttrHandler(items);

        const res = await request(server, 'GET', '/api/compliance/mttr');
        expect(res.statusCode).toBe(200);

        // DEVICE-A 7.1.1 seen_count=3 → '2–3 cycles'
        // DEVICE-A 7.2.1 seen_count=5 → '4–6 cycles'
        // DEVICE-B 7.1.1 seen_count=1 → '1 cycle'
        // DEVICE-C 7.3.1 seen_count=7 → '7+ cycles'
        const buckets = {};
        for (const b of res.body.aging) {
            buckets[b.bucket] = b;
        }

        expect(buckets['1 cycle'].total).toBe(1);
        expect(buckets['2–3 cycles'].total).toBe(1);
        expect(buckets['4–6 cycles'].total).toBe(1);
        expect(buckets['7+ cycles'].total).toBe(1);
    });

    it('7.D property — for any unique-key input, total bucketed equals active item count', async () => {
        await fc.assert(
            fc.asyncProperty(
                fc.array(
                    fc.record({
                        seenCount: fc.integer({ min: 1, max: 20 }),
                        team: fc.constantFrom(...ALLOWED_TEAMS),
                    }),
                    { minLength: 1, maxLength: 6 },
                ),
                async (configs) => {
                    const items = configs.map((cfg, idx) => ({
                        id: 200 + idx, hostname: `PBT-DEVICE-${idx}`, ip_address: `10.5.${idx}.1`,
                        device_type: 'Switch', team: cfg.team,
                        metric_id: `8.${idx + 1}.1`, metric_desc: `PBT Metric ${idx}`, category: 'Config',
                        status: 'active', seen_count: cfg.seenCount, vertical: null,
                        upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null,
                        resolution_date: null, remediation_plan: null, extra_json: '{}',
                    }));
                    installMttrHandler(items);

                    const res = await request(server, 'GET', '/api/compliance/mttr');
                    expect(res.statusCode).toBe(200);

                    const totalBucketed = res.body.aging.reduce((s, b) => s + b.total, 0);
                    expect(totalBucketed).toBe(items.length);
                },
            ),
            { numRuns: 10 },
        );
    });
});


// =============================================================================
// Property 7.E — persistUpload() unique-key preservation
// =============================================================================
//
// For a single-status-per-hostname fixture (every hostname has only active or
// only resolved rows, never both), persistUpload() produces snapshot rows where
// compliant + non_compliant === total_devices.
//
// **Validates: Requirements 3.5**
//
describe('Property 7.E — persistUpload() unique-key preservation', () => {
    it('7.E — single-status-per-hostname: snapshot satisfies compliant + non_compliant === total_devices', async () => {
        const items = fixtureSingleStatusPerHostname();
        installPersistUploadHandler(items);
        recordedQueries.length = 0;

        await persistUpload({
            items: [],
            summary: {},
            reportDate: '2025-06-01',
            filename: 'test-upload.xlsx',
            userId: 1,
            vertical: null,
        });

        // Find the snapshot query and verify its results
        const verticalStatsQuery = recordedQueries.find(q =>
            q.text && /WITH\s+hostname_status\s+AS/i.test(q.text)
        );

        expect(verticalStatsQuery).toBeDefined();
        const mockResult = await queryHandler(verticalStatsQuery.text, verticalStatsQuery.params);

        for (const row of mockResult.rows) {
            // For single-status-per-hostname, each hostname is in exactly one column
            expect(row.compliant + row.non_compliant).toBe(row.total_devices);
        }
    });

    it('7.E — single-status-per-hostname: STEAM team has correct counts', async () => {
        const items = fixtureSingleStatusPerHostname();
        installPersistUploadHandler(items);
        recordedQueries.length = 0;

        await persistUpload({
            items: [],
            summary: {},
            reportDate: '2025-06-01',
            filename: 'test-upload.xlsx',
            userId: 1,
            vertical: null,
        });

        const verticalStatsQuery = recordedQueries.find(q =>
            q.text && /WITH\s+hostname_status\s+AS/i.test(q.text)
        );

        const mockResult = await queryHandler(verticalStatsQuery.text, verticalStatsQuery.params);
        const steamRow = mockResult.rows.find(r => r.team === 'STEAM');
        expect(steamRow).toBeDefined();

        // ACTIVE-ONLY-HOST has 2 active metrics → non_compliant
        // RESOLVED-ONLY-HOST has 1 resolved metric → compliant
        expect(steamRow.total_devices).toBe(2);
        expect(steamRow.non_compliant).toBe(1); // ACTIVE-ONLY-HOST
        expect(steamRow.compliant).toBe(1); // RESOLVED-ONLY-HOST
    });

    it('7.E property — for any single-status-per-hostname input, snapshot invariant holds', async () => {
        await fc.assert(
            fc.asyncProperty(
                fc.array(
                    fc.record({
                        status: fc.constantFrom('active', 'resolved'),
                        team: fc.constantFrom(...ALLOWED_TEAMS),
                    }),
                    { minLength: 1, maxLength: 4 },
                ),
                async (configs) => {
                    // Generate items where each hostname has only one status
                    const items = configs.map((cfg, idx) => ({
                        id: 300 + idx, hostname: `SNAP-HOST-${idx}`, ip_address: `10.3.${idx}.1`,
                        device_type: 'Switch', team: cfg.team,
                        metric_id: `9.${idx + 1}.1`, metric_desc: `Snap Metric ${idx}`, category: 'Config',
                        status: cfg.status, seen_count: idx + 1, vertical: null,
                        upload_id: 100, first_seen_upload_id: 90,
                        resolved_upload_id: cfg.status === 'resolved' ? 100 : null,
                        resolution_date: null, remediation_plan: null, extra_json: '{}',
                    }));
                    installPersistUploadHandler(items);
                    recordedQueries.length = 0;

                    await persistUpload({
                        items: [],
                        summary: {},
                        reportDate: '2025-06-01',
                        filename: 'test-upload.xlsx',
                        userId: 1,
                        vertical: null,
                    });

                    const verticalStatsQuery = recordedQueries.find(q =>
                        q.text && /WITH\s+hostname_status\s+AS/i.test(q.text)
                    );

                    if (verticalStatsQuery) {
                        const mockResult = await queryHandler(verticalStatsQuery.text, verticalStatsQuery.params);
                        for (const row of mockResult.rows) {
                            expect(row.compliant + row.non_compliant).toBeLessThanOrEqual(row.total_devices);
                        }
                    }
                },
            ),
            { numRuns: 10 },
        );
    });
});

// =============================================================================
// Property 7.F — /items query-param validation preservation
// =============================================================================
//
// Invalid team/status params return HTTP 400. Unknown hostname returns HTTP 404.
// These validations must be unchanged by the fix.
//
// **Validates: Requirements 3.1, 3.4**
//
describe('Property 7.F — /items query-param validation preservation', () => {
    it('7.F — invalid team returns HTTP 400', async () => {
        const res = await request(server, 'GET', '/api/compliance/items?team=INVALID_TEAM&status=active');
        expect(res.statusCode).toBe(400);
        expect(res.body.error).toMatch(/Invalid team/i);
    });

    it('7.F — missing team returns HTTP 400', async () => {
        const res = await request(server, 'GET', '/api/compliance/items?status=active');
        expect(res.statusCode).toBe(400);
        expect(res.body.error).toMatch(/team is required/i);
    });

    it('7.F — invalid status returns HTTP 400', async () => {
        const res = await request(server, 'GET', '/api/compliance/items?team=STEAM&status=invalid');
        expect(res.statusCode).toBe(400);
        expect(res.body.error).toMatch(/Invalid status/i);
    });

    it('7.F — unknown hostname returns HTTP 404', async () => {
        // Install handler that returns empty rows for unknown hostname
        installItemsHostnameHandler([]);

        const res = await request(server, 'GET', '/api/compliance/items/NONEXISTENT-HOST-XYZ');
        expect(res.statusCode).toBe(404);
        expect(res.body.error).toMatch(/Device not found/i);
    });

    it('7.F property — for any invalid team name, /items returns 400', async () => {
        await fc.assert(
            fc.asyncProperty(
                fc.string({ minLength: 1, maxLength: 20 }).filter(s =>
                    !['STEAM', 'ACCESS-ENG', 'ACCESS-OPS', 'INTELDEV'].includes(s)
                ),
                async (invalidTeam) => {
                    const encoded = encodeURIComponent(invalidTeam);
                    const res = await request(server, 'GET', `/api/compliance/items?team=${encoded}&status=active`);
                    expect(res.statusCode).toBe(400);
                },
            ),
            { numRuns: 10 },
        );
    });

    it('7.F property — for any invalid status, /items returns 400', async () => {
        await fc.assert(
            fc.asyncProperty(
                fc.string({ minLength: 1, maxLength: 20 }).filter(s =>
                    !['active', 'resolved'].includes(s)
                ),
                async (invalidStatus) => {
                    const encoded = encodeURIComponent(invalidStatus);
                    const res = await request(server, 'GET', `/api/compliance/items?team=STEAM&status=${encoded}`);
                    expect(res.statusCode).toBe(400);
                },
            ),
            { numRuns: 10 },
        );
    });
});

// =============================================================================
// Property 8.A — Representative-row policy on duplicates (SKIPPED)
// =============================================================================
//
// This property asserts post-fix behavior: for inputs WITH duplicates, the
// surviving entry carries seen_count = MAX(seen_count), first_seen = MIN(first_seen),
// last_seen = MAX(last_seen) across the duplicate rows.
//
// SKIP until the fix lands (task 3.8 will unskip this).
//
// **Validates: Requirements 3.2, 3.3**
//
describe('Property 8.A — Representative-row policy on duplicates', () => {
    // eslint-disable-next-line jest/no-disabled-tests
    test.skip('8.A — duplicate rows: surviving entry carries MAX(seen_count)', async () => {
        // This test exercises the duplicate path and asserts the post-fix contract.
        // It will be unskipped in task 3.8 after the fix is implemented.
        const items = [
            {
                id: 1, hostname: 'DUP-DEVICE', ip_address: '10.0.0.1',
                device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1',
                metric_desc: 'Password Complexity', category: 'Configuration',
                status: 'active', seen_count: 3, vertical: null,
                upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null,
                resolution_date: null, remediation_plan: null, extra_json: '{}',
            },
            {
                id: 2, hostname: 'DUP-DEVICE', ip_address: '10.0.0.1',
                device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1',
                metric_desc: 'Password Complexity', category: 'Configuration',
                status: 'active', seen_count: 7, vertical: 'NTS_AEO',
                upload_id: 110, first_seen_upload_id: 80, resolved_upload_id: null,
                resolution_date: null, remediation_plan: null, extra_json: '{}',
            },
        ];
        installItemsHandler(items);

        const res = await request(server, 'GET', '/api/compliance/items?team=STEAM&status=active');
        expect(res.statusCode).toBe(200);

        const device = res.body.devices.find(d => d.hostname === 'DUP-DEVICE');
        expect(device).toBeDefined();

        // After fix: only one entry for metric 7.1.1
        expect(device.failing_metrics.length).toBe(1);
        expect(device.failing_metrics[0].metric_id).toBe('7.1.1');

        // Representative row carries MAX(seen_count) = 7
        expect(device.seen_count).toBe(7);
    });

    // eslint-disable-next-line jest/no-disabled-tests
    test.skip('8.A — duplicate rows on /items/:hostname: surviving entry carries MAX(seen_count)', async () => {
        const items = [
            {
                id: 1, hostname: 'DUP-DEVICE', ip_address: '10.0.0.1',
                device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1',
                metric_desc: 'Password Complexity', category: 'Configuration',
                status: 'active', seen_count: 3, vertical: null,
                upload_id: 100, first_seen_upload_id: 90, resolved_upload_id: null,
                resolution_date: null, remediation_plan: null, extra_json: '{}',
            },
            {
                id: 2, hostname: 'DUP-DEVICE', ip_address: '10.0.0.1',
                device_type: 'Switch', team: 'STEAM', metric_id: '7.1.1',
                metric_desc: 'Password Complexity', category: 'Configuration',
                status: 'active', seen_count: 7, vertical: 'NTS_AEO',
                upload_id: 110, first_seen_upload_id: 80, resolved_upload_id: null,
                resolution_date: null, remediation_plan: null, extra_json: '{}',
            },
        ];
        installItemsHostnameHandler(items);

        const res = await request(server, 'GET', '/api/compliance/items/DUP-DEVICE');
        expect(res.statusCode).toBe(200);

        // After fix: only one entry for (7.1.1, active)
        const activeMetrics = res.body.metrics.filter(m => m.metric_id === '7.1.1' && m.status === 'active');
        expect(activeMetrics.length).toBe(1);

        // Representative row carries MAX(seen_count) = 7
        expect(activeMetrics[0].seen_count).toBe(7);
    });
});
