/**
 * Bug Condition Exploration Property Test: Compliance Remediation Display Fix
 *
 * Spec: .kiro/specs/compliance-remediation-display-fix/ (bugfix)
 *
 * BUG CONDITION:
 *   isBugCondition(row) = row.resolution_date != null OR row.remediation_plan != null
 *   Any row with metadata set will lose it through groupByHostname() because the
 *   function does not propagate resolution_date or remediation_plan into device objects.
 *
 * THIS TEST IS EXPECTED TO FAIL ON UNFIXED CODE.
 * Failure confirms the bug exists — resolution_date and remediation_plan are undefined
 * in the grouped device objects returned by groupByHostname().
 *
 * **Validates: Requirements 1.1, 1.2, 2.2**
 */

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

// --- Mocks (must be installed BEFORE requiring the route 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('../helpers/driftChecker', () => ({
    loadConfig: jest.fn(() => ({})),
    compareSchemaToDrift: jest.fn(() => null),
    reconcileConfig: jest.fn(() => ({ changes: [] })),
}));

jest.mock('../helpers/vclHelpers', () => ({
    isValidDateString: jest.fn(() => true),
    validateRemediationPlan: jest.fn(() => ({ valid: true })),
    computeVCLStats: jest.fn(() => ({})),
    categorizeNonCompliant: jest.fn(() => []),
    rankHeavyHitters: jest.fn(() => []),
    computeForecastBurndown: jest.fn(() => ({})),
    matchByHostname: jest.fn(() => []),
    computeBulkDiff: jest.fn(() => ({})),
    mapColumnHeaders: jest.fn(() => ({})),
}));

const mockPool = {
    query: jest.fn(() => Promise.resolve({ rows: [], rowCount: 0 })),
    connect: jest.fn(() => Promise.resolve({
        query: jest.fn(() => Promise.resolve({ rows: [], rowCount: 0 })),
        release: jest.fn(),
    })),
};
jest.mock('../db', () => mockPool);

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

// --- Generators ---

/** Generate a date string in YYYY-MM-DD format (avoid toISOString on shrunk invalid dates) */
const arbDateString = fc.tuple(
    fc.integer({ min: 2020, max: 2030 }),
    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 non-empty remediation plan string */
const arbRemediationPlan = fc.string({ minLength: 1, maxLength: 200 }).filter(s => s.trim().length > 0);

/** Generate a hostname (alphanumeric with dashes, realistic) */
const arbHostname = fc.stringMatching(/^[A-Z][A-Z0-9\-]{2,20}$/);

/** Generate a metric_id like "7.1.1" or "3.2" */
const arbMetricId = 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 compliance row with non-null resolution_date and/or remediation_plan.
 * This is the bug condition: rows that have metadata which should be propagated.
 */
const arbComplianceRowWithMetadata = fc.record({
    hostname: arbHostname,
    ip_address: fc.ipV4(),
    device_type: fc.constantFrom('Switch', 'Router', 'Firewall', 'Server'),
    team: fc.constantFrom('STEAM', 'ACCESS-ENG'),
    status: fc.constant('active'),
    metric_id: arbMetricId,
    metric_desc: fc.string({ minLength: 3, maxLength: 50 }),
    category: fc.constantFrom('Configuration', 'Patching', 'Access Control'),
    seen_count: fc.integer({ min: 1, max: 20 }),
    first_seen: arbDateString,
    last_seen: arbDateString,
    resolved_on: fc.constant(null),
    resolution_date: arbDateString,
    remediation_plan: arbRemediationPlan,
});

// --- Property Test ---

describe('Bug Condition Exploration: resolution_date and remediation_plan in groupByHostname()', () => {
    it('Property 1: groupByHostname() should propagate resolution_date from rows to device objects', () => {
        fc.assert(
            fc.property(arbComplianceRowWithMetadata, (row) => {
                const rows = [row];
                const noteHostnames = new Set();
                const devices = groupByHostname(rows, noteHostnames);

                // There should be exactly one device for the single hostname
                expect(devices).toHaveLength(1);
                const device = devices[0];

                // BUG CONDITION: resolution_date should be propagated but is undefined on unfixed code
                expect(device.resolution_date).toBe(row.resolution_date);
            }),
            { numRuns: 100 }
        );
    });

    it('Property 2: groupByHostname() should propagate remediation_plan from rows to device objects', () => {
        fc.assert(
            fc.property(arbComplianceRowWithMetadata, (row) => {
                const rows = [row];
                const noteHostnames = new Set();
                const devices = groupByHostname(rows, noteHostnames);

                expect(devices).toHaveLength(1);
                const device = devices[0];

                // BUG CONDITION: remediation_plan should be propagated but is undefined on unfixed code
                expect(device.remediation_plan).toBe(row.remediation_plan);
            }),
            { numRuns: 100 }
        );
    });

    it('Property 3: groupByHostname() should pick first non-null resolution_date across multiple rows for same hostname', () => {
        fc.assert(
            fc.property(
                arbHostname,
                fc.array(arbMetricId, { minLength: 2, maxLength: 5 }),
                arbDateString,
                (hostname, metricIds, resolutionDate) => {
                    // Create multiple rows for the same hostname, first row has resolution_date
                    const rows = metricIds.map((mid, idx) => ({
                        hostname,
                        ip_address: '10.0.0.1',
                        device_type: 'Switch',
                        team: 'STEAM',
                        status: 'active',
                        metric_id: mid,
                        metric_desc: `Metric ${mid}`,
                        category: 'Configuration',
                        seen_count: 1,
                        first_seen: '2025-01-01',
                        last_seen: '2025-06-01',
                        resolved_on: null,
                        resolution_date: idx === 0 ? resolutionDate : null,
                        remediation_plan: null,
                    }));

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

                    expect(devices).toHaveLength(1);
                    const device = devices[0];

                    // The first non-null resolution_date should be propagated
                    expect(device.resolution_date).toBe(resolutionDate);
                }
            ),
            { numRuns: 100 }
        );
    });

    it('Property 4: groupByHostname() should pick first non-null remediation_plan across multiple rows for same hostname', () => {
        fc.assert(
            fc.property(
                arbHostname,
                fc.array(arbMetricId, { minLength: 2, maxLength: 5 }),
                arbRemediationPlan,
                (hostname, metricIds, plan) => {
                    // Create multiple rows for the same hostname, first row has remediation_plan
                    const rows = metricIds.map((mid, idx) => ({
                        hostname,
                        ip_address: '10.0.0.1',
                        device_type: 'Switch',
                        team: 'STEAM',
                        status: 'active',
                        metric_id: mid,
                        metric_desc: `Metric ${mid}`,
                        category: 'Configuration',
                        seen_count: 1,
                        first_seen: '2025-01-01',
                        last_seen: '2025-06-01',
                        resolved_on: null,
                        resolution_date: null,
                        remediation_plan: idx === 0 ? plan : null,
                    }));

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

                    expect(devices).toHaveLength(1);
                    const device = devices[0];

                    // The first non-null remediation_plan should be propagated
                    expect(device.remediation_plan).toBe(plan);
                }
            ),
            { numRuns: 100 }
        );
    });
});
