/**
 * Property-Based Tests: Ivanti Queue Remediation — Notes System
 *
 * Feature: ivanti-queue-remediation
 *
 * Tests properties 3–7 from the design document:
 * - Property 3: Whitespace-only note content is always rejected
 * - Property 4: Note creation round-trip
 * - Property 5: Notes returned in descending creation order
 * - Property 6: Ownership enforcement
 * - Property 7: Cascade delete removes all associated notes
 *
 * These tests validate the pure logic and simulate the API behavior
 * without requiring a running database.
 */

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

// ---------------------------------------------------------------------------
// Simulate the backend validation and data layer logic
// ---------------------------------------------------------------------------

/**
 * Simulates POST /api/ivanti/todo-queue/:id/notes validation logic.
 * Returns { accepted: boolean, status: number, error?: string }
 */
function validateNoteCreation(note_text, queueItemExists, isOwner) {
    // Ownership check
    if (!queueItemExists || !isOwner) {
        return { accepted: false, status: 404, error: 'Queue item not found.' };
    }

    // Text validation
    if (!note_text || typeof note_text !== 'string' || note_text.trim().length === 0) {
        return { accepted: false, status: 400, error: 'Note text is required.' };
    }
    if (note_text.length > 5000) {
        return { accepted: false, status: 400, error: 'Note text must not exceed 5000 characters.' };
    }

    return { accepted: true, status: 201 };
}

/**
 * Simulates a simple in-memory note store for round-trip and ordering tests.
 */
class NoteStore {
    constructor() {
        this.notes = [];
        this.nextId = 1;
        this.queueItems = new Map(); // id -> { user_id }
    }

    createQueueItem(userId) {
        const id = this.nextId++;
        this.queueItems.set(id, { user_id: userId });
        return id;
    }

    createNote(queueItemId, userId, username, noteText) {
        const item = this.queueItems.get(queueItemId);
        if (!item) return { error: 'Queue item not found.', status: 404 };
        if (item.user_id !== userId) return { error: 'Queue item not found.', status: 404 };
        if (!noteText || typeof noteText !== 'string' || noteText.trim().length === 0) {
            return { error: 'Note text is required.', status: 400 };
        }
        if (noteText.length > 5000) {
            return { error: 'Note text must not exceed 5000 characters.', status: 400 };
        }

        const note = {
            id: this.nextId++,
            queue_item_id: queueItemId,
            user_id: userId,
            username,
            note_text: noteText,
            created_at: new Date().toISOString(),
        };
        this.notes.push(note);
        return { note, status: 201 };
    }

    getNotes(queueItemId, userId) {
        const item = this.queueItems.get(queueItemId);
        if (!item) return { error: 'Queue item not found.', status: 404 };
        if (item.user_id !== userId) return { error: 'Queue item not found.', status: 404 };

        const notes = this.notes
            .filter(n => n.queue_item_id === queueItemId)
            .sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
        return { notes, status: 200 };
    }

    deleteQueueItem(queueItemId) {
        this.queueItems.delete(queueItemId);
        // Simulate ON DELETE CASCADE
        this.notes = this.notes.filter(n => n.queue_item_id !== queueItemId);
    }

    getNotesForItem(queueItemId) {
        return this.notes.filter(n => n.queue_item_id === queueItemId);
    }
}

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

// Whitespace-only strings (including empty)
const arbWhitespaceOnly = fc.oneof(
    fc.constant(''),
    fc.array(fc.constantFrom(' ', '\t', '\n', '\r', '\f'), { minLength: 1, maxLength: 50 })
        .map(arr => arr.join(''))
);

// Valid note text: 1–5000 chars, at least one non-whitespace
const arbValidNoteText = fc.string({ minLength: 1, maxLength: 200 }).filter(s => s.trim().length > 0);

// Over-length note text
const arbOverlengthNoteText = fc.string({ minLength: 5001, maxLength: 5100 });

// User IDs (positive integers)
const arbUserId = fc.integer({ min: 1, max: 10000 });

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

// Number of notes to create (for ordering test)
const arbNoteCount = fc.integer({ min: 2, max: 10 });

// ---------------------------------------------------------------------------
// Property 3: Whitespace-only note content is always rejected
// **Validates: Requirements 3.5, 4.4, 5.8**
// ---------------------------------------------------------------------------
describe('Feature: ivanti-queue-remediation, Property 3: Whitespace-only note content is always rejected', () => {
    it('rejects any string composed entirely of whitespace characters', () => {
        fc.assert(
            fc.property(arbWhitespaceOnly, (noteText) => {
                const result = validateNoteCreation(noteText, true, true);
                expect(result.accepted).toBe(false);
                expect(result.status).toBe(400);
            }),
            { numRuns: 100 }
        );
    });

    it('rejects undefined and null as note_text', () => {
        const arbNullish = fc.oneof(fc.constant(undefined), fc.constant(null));
        fc.assert(
            fc.property(arbNullish, (noteText) => {
                const result = validateNoteCreation(noteText, true, true);
                expect(result.accepted).toBe(false);
                expect(result.status).toBe(400);
            }),
            { numRuns: 10 }
        );
    });
});

// ---------------------------------------------------------------------------
// Property 4: Note creation round-trip
// **Validates: Requirements 4.1, 3.3**
// ---------------------------------------------------------------------------
describe('Feature: ivanti-queue-remediation, Property 4: Note creation round-trip', () => {
    it('creates a note and retrieves it with exact same text, correct username, and valid created_at', () => {
        fc.assert(
            fc.property(arbValidNoteText, arbUserId, arbUsername, (noteText, userId, username) => {
                const store = new NoteStore();
                const itemId = store.createQueueItem(userId);

                const createResult = store.createNote(itemId, userId, username, noteText);
                expect(createResult.status).toBe(201);
                expect(createResult.note.note_text).toBe(noteText);
                expect(createResult.note.username).toBe(username);

                const getResult = store.getNotes(itemId, userId);
                expect(getResult.status).toBe(200);
                expect(getResult.notes.length).toBe(1);
                expect(getResult.notes[0].note_text).toBe(noteText);
                expect(getResult.notes[0].username).toBe(username);
                expect(getResult.notes[0].created_at).toBeTruthy();
                // Verify created_at is a valid ISO timestamp
                expect(new Date(getResult.notes[0].created_at).toString()).not.toBe('Invalid Date');
            }),
            { numRuns: 100 }
        );
    });
});

// ---------------------------------------------------------------------------
// Property 5: Notes returned in descending creation order
// **Validates: Requirements 4.2**
// ---------------------------------------------------------------------------
describe('Feature: ivanti-queue-remediation, Property 5: Notes returned in descending creation order', () => {
    it('for N >= 2 notes, GET returns them with created_at in descending order', () => {
        fc.assert(
            fc.property(arbNoteCount, arbUserId, arbUsername, (count, userId, username) => {
                const store = new NoteStore();
                const itemId = store.createQueueItem(userId);

                // Create N notes
                for (let i = 0; i < count; i++) {
                    const result = store.createNote(itemId, userId, username, `Note ${i + 1}`);
                    expect(result.status).toBe(201);
                }

                const getResult = store.getNotes(itemId, userId);
                expect(getResult.status).toBe(200);
                expect(getResult.notes.length).toBe(count);

                // Verify descending order
                for (let i = 0; i < getResult.notes.length - 1; i++) {
                    const current = new Date(getResult.notes[i].created_at);
                    const next = new Date(getResult.notes[i + 1].created_at);
                    expect(current.getTime()).toBeGreaterThanOrEqual(next.getTime());
                }
            }),
            { numRuns: 100 }
        );
    });
});

// ---------------------------------------------------------------------------
// Property 6: Ownership enforcement
// **Validates: Requirements 4.3**
// ---------------------------------------------------------------------------
describe('Feature: ivanti-queue-remediation, Property 6: Ownership enforcement', () => {
    it('returns 404 when user B attempts to create notes on user A queue item', () => {
        fc.assert(
            fc.property(
                arbUserId,
                arbUserId.filter(id => id > 1), // ensure we can generate different users
                arbValidNoteText,
                arbUsername,
                (userA, userBOffset, noteText, username) => {
                    const userB = userA + userBOffset; // guarantee different user
                    const store = new NoteStore();
                    const itemId = store.createQueueItem(userA);

                    const result = store.createNote(itemId, userB, username, noteText);
                    expect(result.status).toBe(404);
                    expect(result.error).toBe('Queue item not found.');
                }
            ),
            { numRuns: 100 }
        );
    });

    it('returns 404 when user B attempts to get notes on user A queue item', () => {
        fc.assert(
            fc.property(
                arbUserId,
                arbUserId.filter(id => id > 1),
                arbValidNoteText,
                arbUsername,
                (userA, userBOffset, noteText, username) => {
                    const userB = userA + userBOffset;
                    const store = new NoteStore();
                    const itemId = store.createQueueItem(userA);

                    // User A creates a note
                    store.createNote(itemId, userA, username, noteText);

                    // User B tries to read
                    const result = store.getNotes(itemId, userB);
                    expect(result.status).toBe(404);
                    expect(result.error).toBe('Queue item not found.');
                }
            ),
            { numRuns: 100 }
        );
    });
});

// ---------------------------------------------------------------------------
// Property 7: Cascade delete removes all associated notes
// **Validates: Requirements 3.4, 7.3**
// ---------------------------------------------------------------------------
describe('Feature: ivanti-queue-remediation, Property 7: Cascade delete removes all associated notes', () => {
    it('deleting a queue item removes all its associated notes', () => {
        fc.assert(
            fc.property(
                arbNoteCount,
                arbUserId,
                arbUsername,
                (count, userId, username) => {
                    const store = new NoteStore();
                    const itemId = store.createQueueItem(userId);

                    // Create N notes
                    for (let i = 0; i < count; i++) {
                        store.createNote(itemId, userId, username, `Remediation step ${i + 1}`);
                    }

                    // Verify notes exist
                    expect(store.getNotesForItem(itemId).length).toBe(count);

                    // Delete the queue item (simulates CASCADE)
                    store.deleteQueueItem(itemId);

                    // Verify zero notes remain
                    expect(store.getNotesForItem(itemId).length).toBe(0);
                }
            ),
            { numRuns: 100 }
        );
    });

    it('deleting a queue item does not affect notes for other queue items', () => {
        fc.assert(
            fc.property(
                arbNoteCount,
                arbUserId,
                arbUsername,
                (count, userId, username) => {
                    const store = new NoteStore();
                    const itemA = store.createQueueItem(userId);
                    const itemB = store.createQueueItem(userId);

                    // Create notes for both items
                    for (let i = 0; i < count; i++) {
                        store.createNote(itemA, userId, username, `Note A-${i}`);
                        store.createNote(itemB, userId, username, `Note B-${i}`);
                    }

                    // Delete item A
                    store.deleteQueueItem(itemA);

                    // Item B notes are unaffected
                    expect(store.getNotesForItem(itemB).length).toBe(count);
                    expect(store.getNotesForItem(itemA).length).toBe(0);
                }
            ),
            { numRuns: 100 }
        );
    });
});
