/**
 * Property-Based Tests: Ivanti Queue Remediation — Description Generation
 *
 * Feature: ivanti-queue-remediation
 *
 * Property 10: Description generation appends remediation notes iff notes exist
 * Property 11: Non-Remediate description unchanged
 *
 * **Validates: Requirements 8.1, 8.2, 8.3, 8.4**
 */
import fc from 'fast-check';
import { generateConsolidatedDescription, appendRemediationNotes } from '../utils/jiraConsolidation';

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

const arbUsername = fc.string({ minLength: 1, maxLength: 30 }).filter(s => s.trim().length > 0);

const arbNoteText = fc.string({ minLength: 1, maxLength: 200 }).filter(s => s.trim().length > 0);

const arbDate = fc.integer({ min: 1577836800000, max: 1924905600000 })
    .map(ts => new Date(ts).toISOString());

const arbNote = fc.record({
    id: fc.integer({ min: 1, max: 100000 }),
    queue_item_id: fc.integer({ min: 1, max: 10000 }),
    user_id: fc.integer({ min: 1, max: 1000 }),
    username: arbUsername,
    note_text: arbNoteText,
    created_at: arbDate,
});

const arbQueueItem = fc.record({
    id: fc.integer({ min: 1, max: 10000 }),
    finding_id: fc.string({ minLength: 1, maxLength: 20 }),
    finding_title: fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
    vendor: fc.constantFrom('Microsoft', 'Cisco', 'Juniper', 'Adobe'),
    workflow_type: fc.constant('Remediate'),
    hostname: fc.constantFrom('server01', 'host-a', 'web-prod'),
    ip_address: fc.constantFrom('10.0.0.1', '192.168.1.5', '172.16.0.10'),
    cves_json: fc.constant(JSON.stringify(['CVE-2024-1234'])),
});

const arbNonRemediateItem = fc.record({
    id: fc.integer({ min: 1, max: 10000 }),
    finding_id: fc.string({ minLength: 1, maxLength: 20 }),
    finding_title: fc.string({ minLength: 1, maxLength: 100 }).filter(s => s.trim().length > 0),
    vendor: fc.constantFrom('Microsoft', 'Cisco', 'Juniper', 'Adobe'),
    workflow_type: fc.constantFrom('FP', 'Archer', 'CARD', 'GRANITE', 'DECOM'),
    hostname: fc.constantFrom('server01', 'host-a', 'web-prod'),
    ip_address: fc.constantFrom('10.0.0.1', '192.168.1.5', '172.16.0.10'),
    cves_json: fc.constant(JSON.stringify(['CVE-2024-5678'])),
});

// ---------------------------------------------------------------------------
// Property 10: Description generation appends remediation notes iff notes exist
// **Validates: Requirements 8.1, 8.2, 8.3**
// ---------------------------------------------------------------------------
describe('Feature: ivanti-queue-remediation, Property 10: Description generation appends remediation notes iff notes exist', () => {
    it('appends a "Remediation Notes" section when notesMap has at least one note', () => {
        fc.assert(
            fc.property(
                fc.array(arbQueueItem, { minLength: 1, maxLength: 5 })
                    .map(items => {
                        const seen = new Set();
                        return items.filter(i => { if (seen.has(i.id)) return false; seen.add(i.id); return true; });
                    })
                    .filter(items => items.length > 0),
                fc.array(arbNote, { minLength: 1, maxLength: 5 }),
                (items, notes) => {
                    const baseDescription = generateConsolidatedDescription(items);
                    // Map notes to the first item
                    const notesMap = { [items[0].id]: notes };
                    const result = appendRemediationNotes(baseDescription, notesMap);

                    // Should contain the remediation notes section
                    expect(result).toContain('== Remediation Notes ==');
                    // Should still contain the base description
                    expect(result).toContain(baseDescription.trim());
                    // Each note's text should appear
                    for (const note of notes) {
                        expect(result).toContain(note.note_text);
                        expect(result).toContain(note.username);
                    }
                }
            ),
            { numRuns: 100 }
        );
    });

    it('notes are listed in chronological order (oldest first) with [YYYY-MM-DD] prefix', () => {
        fc.assert(
            fc.property(
                arbQueueItem,
                fc.array(arbNote, { minLength: 2, maxLength: 5 }),
                (item, notes) => {
                    const baseDescription = generateConsolidatedDescription([item]);
                    const notesMap = { [item.id]: notes };
                    const result = appendRemediationNotes(baseDescription, notesMap);

                    // Extract the remediation notes section
                    const section = result.split('== Remediation Notes ==')[1];
                    expect(section).toBeDefined();

                    // Verify each note has the [YYYY-MM-DD] format prefix
                    for (const note of notes) {
                        const expectedDate = new Date(note.created_at).toISOString().slice(0, 10);
                        expect(section).toContain(`[${expectedDate}] ${note.username}:`);
                    }

                    // Verify chronological order (oldest first)
                    const sortedNotes = [...notes].sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
                    let lastIndex = -1;
                    for (const note of sortedNotes) {
                        const idx = section.indexOf(note.note_text, lastIndex + 1);
                        expect(idx).toBeGreaterThan(lastIndex);
                        lastIndex = idx;
                    }
                }
            ),
            { numRuns: 100 }
        );
    });

    it('does NOT append a section when notesMap is empty', () => {
        fc.assert(
            fc.property(
                fc.array(arbQueueItem, { minLength: 1, maxLength: 3 })
                    .map(items => {
                        const seen = new Set();
                        return items.filter(i => { if (seen.has(i.id)) return false; seen.add(i.id); return true; });
                    })
                    .filter(items => items.length > 0),
                (items) => {
                    const baseDescription = generateConsolidatedDescription(items);
                    const result = appendRemediationNotes(baseDescription, {});

                    expect(result).toBe(baseDescription);
                    expect(result).not.toContain('== Remediation Notes ==');
                }
            ),
            { numRuns: 100 }
        );
    });

    it('does NOT append a section when notesMap has items with empty arrays', () => {
        fc.assert(
            fc.property(
                arbQueueItem,
                (item) => {
                    const baseDescription = generateConsolidatedDescription([item]);
                    const notesMap = { [item.id]: [] };
                    const result = appendRemediationNotes(baseDescription, notesMap);

                    expect(result).toBe(baseDescription);
                    expect(result).not.toContain('== Remediation Notes ==');
                }
            ),
            { numRuns: 100 }
        );
    });
});

// ---------------------------------------------------------------------------
// Property 11: Non-Remediate description unchanged
// **Validates: Requirements 8.4**
// ---------------------------------------------------------------------------
describe('Feature: ivanti-queue-remediation, Property 11: Non-Remediate description unchanged', () => {
    it('output is identical to generateConsolidatedDescription when no notes exist', () => {
        fc.assert(
            fc.property(
                fc.array(arbNonRemediateItem, { minLength: 1, maxLength: 5 })
                    .map(items => {
                        const seen = new Set();
                        return items.filter(i => { if (seen.has(i.id)) return false; seen.add(i.id); return true; });
                    })
                    .filter(items => items.length > 0),
                (items) => {
                    const baseDescription = generateConsolidatedDescription(items);
                    // Empty notesMap — simulates non-Remediate items
                    const result = appendRemediationNotes(baseDescription, {});

                    expect(result).toBe(baseDescription);
                    expect(result).not.toContain('Remediation Notes');
                }
            ),
            { numRuns: 100 }
        );
    });

    it('output is identical when notesMap is null or undefined', () => {
        fc.assert(
            fc.property(
                fc.array(arbNonRemediateItem, { minLength: 1, maxLength: 3 })
                    .map(items => {
                        const seen = new Set();
                        return items.filter(i => { if (seen.has(i.id)) return false; seen.add(i.id); return true; });
                    })
                    .filter(items => items.length > 0),
                fc.oneof(fc.constant(null), fc.constant(undefined)),
                (items, notesMap) => {
                    const baseDescription = generateConsolidatedDescription(items);
                    const result = appendRemediationNotes(baseDescription, notesMap);

                    expect(result).toBe(baseDescription);
                }
            ),
            { numRuns: 50 }
        );
    });
});
