/**
 * Unit and Integration Tests: FP Submissions Cleanup
 *
 * Feature: fp-submissions-cleanup
 *
 * Tests cover:
 * - Dismiss endpoint (happy path, wrong status, ownership check, not found)
 * - Filter edge cases (all approved, all dismissed, mixed, empty array)
 * - Integration: dismissed submissions remain in DB but are excluded from filtered list
 */

const http = require('http');
const express = require('express');

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

// Mock audit log as a no-op
jest.mock('../helpers/auditLog', () => jest.fn());

// Mock ivantiApi to avoid real network calls
jest.mock('../helpers/ivantiApi', () => ({
    ivantiFormPost: jest.fn(),
    ivantiPost: jest.fn(),
}));

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

const createIvantiFpWorkflowRouter = require('../routes/ivantiFpWorkflow');
const { filterVisibleSubmissions, shouldShowDismissButton } = require('../routes/ivantiFpWorkflow');

// --- HTTP helper ---

function request(server, method, path, body) {
    return new Promise((resolve, reject) => {
        const addr = server.address();
        const options = {
            hostname: '127.0.0.1',
            port: addr.port,
            path,
            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 body = Buffer.concat(chunks).toString();
                let json;
                try { json = JSON.parse(body); } catch (e) { json = null; }
                resolve({ statusCode: res.statusCode, body: json });
            });
        });

        req.on('error', reject);
        if (body) req.write(JSON.stringify(body));
        req.end();
    });
}

// --- Dismiss Endpoint Tests (Task 8.1) ---

describe('PATCH /submissions/:id/dismiss', () => {
    let app, server;

    beforeAll((done) => {
        app = express();
        app.use(express.json());
        app.use('/api/ivanti/fp-workflow', createIvantiFpWorkflowRouter());
        server = app.listen(0, '127.0.0.1', done);
    });

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

    beforeEach(() => {
        mockPool.query.mockReset();
    });

    it('happy path — dismisses a rejected submission owned by the user', async () => {
        // First query: SELECT submission
        mockPool.query.mockResolvedValueOnce({
            rows: [{
                id: 42,
                user_id: 1,
                lifecycle_status: 'rejected',
                dismissed_at: null,
                ivanti_workflow_batch_id: 'WF-100'
            }],
        });
        // Second query: UPDATE dismissed_at
        mockPool.query.mockResolvedValueOnce({ rowCount: 1 });

        const res = await request(server, 'PATCH', '/api/ivanti/fp-workflow/submissions/42/dismiss');

        expect(res.statusCode).toBe(200);
        expect(res.body).toEqual({ success: true });
        // Verify the UPDATE was called with the correct SQL pattern
        expect(mockPool.query).toHaveBeenCalledTimes(2);
        const updateCall = mockPool.query.mock.calls[1];
        expect(updateCall[0]).toContain('dismissed_at');
        expect(updateCall[1]).toContain('42');
    });

    it('returns 404 when submission does not exist', async () => {
        mockPool.query.mockResolvedValueOnce({ rows: [] });

        const res = await request(server, 'PATCH', '/api/ivanti/fp-workflow/submissions/999/dismiss');

        expect(res.statusCode).toBe(404);
        expect(res.body.error).toBe('Submission not found.');
    });

    it('returns 403 when user does not own the submission', async () => {
        mockPool.query.mockResolvedValueOnce({
            rows: [{
                id: 42,
                user_id: 99, // different user
                lifecycle_status: 'rejected',
                dismissed_at: null,
                ivanti_workflow_batch_id: 'WF-100'
            }],
        });

        const res = await request(server, 'PATCH', '/api/ivanti/fp-workflow/submissions/42/dismiss');

        expect(res.statusCode).toBe(403);
        expect(res.body.error).toBe('You can only dismiss your own submissions.');
    });

    it('returns 400 when submission is not in rejected status', async () => {
        mockPool.query.mockResolvedValueOnce({
            rows: [{
                id: 42,
                user_id: 1,
                lifecycle_status: 'submitted',
                dismissed_at: null,
                ivanti_workflow_batch_id: 'WF-100'
            }],
        });

        const res = await request(server, 'PATCH', '/api/ivanti/fp-workflow/submissions/42/dismiss');

        expect(res.statusCode).toBe(400);
        expect(res.body.error).toBe('Only rejected submissions can be dismissed.');
    });
});

// --- Filter Edge Cases (Task 8.2) ---

describe('filterVisibleSubmissions — edge cases', () => {
    it('returns empty array when all submissions are approved', () => {
        const submissions = [
            { id: 1, lifecycle_status: 'approved', dismissed_at: null },
            { id: 2, lifecycle_status: 'approved', dismissed_at: null },
            { id: 3, lifecycle_status: 'approved', dismissed_at: null },
        ];
        expect(filterVisibleSubmissions(submissions)).toEqual([]);
    });

    it('returns empty array when all submissions are dismissed', () => {
        const submissions = [
            { id: 1, lifecycle_status: 'rejected', dismissed_at: '2026-05-01T12:00:00Z' },
            { id: 2, lifecycle_status: 'submitted', dismissed_at: '2026-04-15T08:00:00Z' },
            { id: 3, lifecycle_status: 'rework', dismissed_at: '2026-03-20T10:00:00Z' },
        ];
        expect(filterVisibleSubmissions(submissions)).toEqual([]);
    });

    it('returns correct subset for mixed statuses', () => {
        const submissions = [
            { id: 1, lifecycle_status: 'approved', dismissed_at: null },
            { id: 2, lifecycle_status: 'rejected', dismissed_at: null },
            { id: 3, lifecycle_status: 'submitted', dismissed_at: '2026-05-01T12:00:00Z' },
            { id: 4, lifecycle_status: 'rework', dismissed_at: null },
            { id: 5, lifecycle_status: 'resubmitted', dismissed_at: null },
        ];
        const result = filterVisibleSubmissions(submissions);
        expect(result).toEqual([
            { id: 2, lifecycle_status: 'rejected', dismissed_at: null },
            { id: 4, lifecycle_status: 'rework', dismissed_at: null },
            { id: 5, lifecycle_status: 'resubmitted', dismissed_at: null },
        ]);
    });

    it('returns empty array for empty input', () => {
        expect(filterVisibleSubmissions([])).toEqual([]);
    });
});

// --- Integration Test (Task 8.3) ---

describe('Integration: dismissed submissions remain in DB but are excluded from filtered list', () => {
    it('dismissed submission is still in the database but excluded by filterVisibleSubmissions', async () => {
        // Simulate the full database state after a dismiss operation:
        // The submission record still exists with dismissed_at set
        const allSubmissionsInDb = [
            { id: 1, lifecycle_status: 'submitted', dismissed_at: null, user_id: 1 },
            { id: 2, lifecycle_status: 'rejected', dismissed_at: '2026-05-01T12:00:00Z', user_id: 1 },
            { id: 3, lifecycle_status: 'approved', dismissed_at: null, user_id: 1 },
            { id: 4, lifecycle_status: 'rejected', dismissed_at: null, user_id: 1 },
        ];

        // The dismissed submission (id: 2) is still in the database
        const dismissedSubmission = allSubmissionsInDb.find(s => s.id === 2);
        expect(dismissedSubmission).toBeDefined();
        expect(dismissedSubmission.dismissed_at).not.toBeNull();

        // But when we filter for visible submissions, it's excluded
        const visibleSubmissions = filterVisibleSubmissions(allSubmissionsInDb);

        // Dismissed submission (id: 2) is NOT in the visible list
        expect(visibleSubmissions.find(s => s.id === 2)).toBeUndefined();

        // Approved submission (id: 3) is also NOT in the visible list
        expect(visibleSubmissions.find(s => s.id === 3)).toBeUndefined();

        // Non-dismissed, non-approved submissions ARE in the visible list
        expect(visibleSubmissions).toEqual([
            { id: 1, lifecycle_status: 'submitted', dismissed_at: null, user_id: 1 },
            { id: 4, lifecycle_status: 'rejected', dismissed_at: null, user_id: 1 },
        ]);

        // Verify the original array is unchanged (submissions remain in DB)
        expect(allSubmissionsInDb.length).toBe(4);
        expect(allSubmissionsInDb.find(s => s.id === 2)).toBeDefined();
    });
});
