/**
 * Unit and Integration Tests: VCL Aggregated Burndown
 *
 * Feature: vcl-aggregated-burndown
 *
 * Tests cover:
 * - deduplicateByHostname edge cases
 * - computeAggregatedBurndown edge cases
 * - GET /burndown endpoint with mocked DB
 * - Empty DB returns zero/empty response
 * - All-blocker scenario
 * - Auth middleware enforcement
 */

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

// Mock auth middleware
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());
jest.mock('../helpers/ivantiApi', () => ({
    ivantiFormPost: jest.fn(),
    ivantiPost: jest.fn(),
}));

// Mock driftChecker
jest.mock('../helpers/driftChecker', () => ({
    loadConfig: jest.fn(() => ({})),
    compareSchemaToDrift: jest.fn(() => null),
    reconcileConfig: jest.fn(() => ({ changes: [] })),
}));

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 {
    deduplicateByHostname,
    computeAggregatedBurndown,
} = require('../helpers/vclHelpers');

const { createVCLMultiVerticalRouter } = require('../routes/vclMultiVertical');

// --- 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 rawBody = Buffer.concat(chunks).toString();
                let json;
                try { json = JSON.parse(rawBody); } catch (e) { json = null; }
                resolve({ statusCode: res.statusCode, body: json });
            });
        });

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

// --- Setup ---

let app, server;

beforeAll((done) => {
    app = express();
    app.use(express.json());

    const mockUpload = { array: () => (req, res, next) => next() };
    const router = createVCLMultiVerticalRouter(mockUpload);
    app.use('/api/compliance/vcl-multi', router);

    server = app.listen(0, '127.0.0.1', done);
});

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

beforeEach(() => {
    mockPool.query.mockReset();
    mockPool.connect.mockReset();
    mockPool.query.mockResolvedValue({ rows: [], rowCount: 0 });
});

// --- deduplicateByHostname unit tests ---

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

    it('passes through single item unchanged', () => {
        const items = [{ hostname: 'srv-001', resolution_date: '2026-06-15', vertical: 'NTS_AEO' }];
        const result = deduplicateByHostname(items);
        expect(result).toEqual([{ hostname: 'srv-001', resolution_date: '2026-06-15', vertical: 'NTS_AEO' }]);
    });

    it('deduplicates by hostname keeping earliest non-null date', () => {
        const items = [
            { hostname: 'srv-001', resolution_date: '2026-08-15', vertical: 'NTS_AEO' },
            { hostname: 'srv-001', resolution_date: '2026-06-01', vertical: 'SDIT_CISO' },
            { hostname: 'srv-001', resolution_date: '2026-07-10', vertical: 'TSI' },
        ];
        const result = deduplicateByHostname(items);
        expect(result).toHaveLength(1);
        expect(result[0].hostname).toBe('srv-001');
        expect(result[0].resolution_date).toBe('2026-06-01');
    });

    it('returns null date when all entries for a hostname have null dates', () => {
        const items = [
            { hostname: 'srv-001', resolution_date: null, vertical: 'NTS_AEO' },
            { hostname: 'srv-001', resolution_date: null, vertical: 'SDIT_CISO' },
        ];
        const result = deduplicateByHostname(items);
        expect(result).toHaveLength(1);
        expect(result[0].resolution_date).toBeNull();
    });

    it('picks earliest non-null date even when some entries are null', () => {
        const items = [
            { hostname: 'srv-001', resolution_date: null, vertical: 'NTS_AEO' },
            { hostname: 'srv-001', resolution_date: '2026-09-01', vertical: 'SDIT_CISO' },
            { hostname: 'srv-001', resolution_date: '2026-06-15', vertical: 'TSI' },
        ];
        const result = deduplicateByHostname(items);
        expect(result).toHaveLength(1);
        expect(result[0].resolution_date).toBe('2026-06-15');
    });

    it('preserves vertical from the first entry', () => {
        const items = [
            { hostname: 'srv-001', resolution_date: '2026-08-01', vertical: 'NTS_AEO' },
            { hostname: 'srv-001', resolution_date: '2026-06-01', vertical: 'SDIT_CISO' },
        ];
        const result = deduplicateByHostname(items);
        expect(result[0].vertical).toBe('NTS_AEO');
    });
});

// --- computeAggregatedBurndown unit tests ---

describe('computeAggregatedBurndown', () => {
    it('returns zero/empty for empty input', () => {
        const result = computeAggregatedBurndown([]);
        expect(result.total).toBe(0);
        expect(result.blockers).toBe(0);
        expect(result.with_dates).toBe(0);
        expect(result.monthly).toEqual({});
        expect(result.projection).toEqual({});
        expect(result.projected_clear_date).toBeNull();
        expect(result.by_vertical).toEqual([]);
    });

    it('all blockers — with_dates=0, monthly={}, projected_clear_date=null', () => {
        const devices = [
            { hostname: 'srv-001', resolution_date: null, vertical: 'NTS_AEO' },
            { hostname: 'srv-002', resolution_date: null, vertical: 'NTS_AEO' },
            { hostname: 'srv-003', resolution_date: null, vertical: 'SDIT_CISO' },
        ];
        const result = computeAggregatedBurndown(devices);
        expect(result.total).toBe(3);
        expect(result.blockers).toBe(3);
        expect(result.with_dates).toBe(0);
        expect(result.monthly).toEqual({});
        expect(result.projected_clear_date).toBeNull();
    });

    it('single device with date — correct monthly bucket and projection', () => {
        const devices = [
            { hostname: 'srv-001', resolution_date: '2026-06-15', vertical: 'NTS_AEO' },
        ];
        const result = computeAggregatedBurndown(devices);
        expect(result.total).toBe(1);
        expect(result.blockers).toBe(0);
        expect(result.with_dates).toBe(1);
        expect(result.monthly).toEqual({ '2026-06': 1 });
        expect(result.projection).toEqual({ '2026-06': { remediated: 1, remaining: 0 } });
        expect(result.projected_clear_date).toBe('2026-06');
    });

    it('mixed blockers and in-progress — projected_clear_date is null', () => {
        const devices = [
            { hostname: 'srv-001', resolution_date: '2026-06-15', vertical: 'NTS_AEO' },
            { hostname: 'srv-002', resolution_date: null, vertical: 'NTS_AEO' },
        ];
        const result = computeAggregatedBurndown(devices);
        expect(result.total).toBe(2);
        expect(result.blockers).toBe(1);
        expect(result.with_dates).toBe(1);
        expect(result.projected_clear_date).toBeNull();
    });

    it('multiple months — correct cumulative projection', () => {
        const devices = [
            { hostname: 'srv-001', resolution_date: '2026-06-15', vertical: 'NTS_AEO' },
            { hostname: 'srv-002', resolution_date: '2026-06-20', vertical: 'NTS_AEO' },
            { hostname: 'srv-003', resolution_date: '2026-07-10', vertical: 'SDIT_CISO' },
            { hostname: 'srv-004', resolution_date: '2026-08-01', vertical: 'TSI' },
        ];
        const result = computeAggregatedBurndown(devices);
        expect(result.total).toBe(4);
        expect(result.monthly).toEqual({ '2026-06': 2, '2026-07': 1, '2026-08': 1 });
        expect(result.projection['2026-06'].remaining).toBe(2); // 4 - 2
        expect(result.projection['2026-07'].remaining).toBe(1); // 4 - 3
        expect(result.projection['2026-08'].remaining).toBe(0); // 4 - 4
        expect(result.projected_clear_date).toBe('2026-08');
    });

    it('by_vertical sorted descending by total, omits zero-total verticals', () => {
        const devices = [
            { hostname: 'srv-001', resolution_date: '2026-06-15', vertical: 'TSI' },
            { hostname: 'srv-002', resolution_date: null, vertical: 'NTS_AEO' },
            { hostname: 'srv-003', resolution_date: null, vertical: 'NTS_AEO' },
            { hostname: 'srv-004', resolution_date: '2026-07-01', vertical: 'NTS_AEO' },
        ];
        const result = computeAggregatedBurndown(devices);
        expect(result.by_vertical[0].vertical).toBe('NTS_AEO');
        expect(result.by_vertical[0].total).toBe(3);
        expect(result.by_vertical[1].vertical).toBe('TSI');
        expect(result.by_vertical[1].total).toBe(1);
    });
});

// --- GET /burndown endpoint tests ---

describe('GET /api/compliance/vcl-multi/burndown', () => {
    it('returns zero/empty response when no active devices exist', async () => {
        mockPool.query.mockResolvedValueOnce({ rows: [] });

        const res = await request(server, 'GET', '/api/compliance/vcl-multi/burndown');

        expect(res.statusCode).toBe(200);
        expect(res.body.total_non_compliant).toBe(0);
        expect(res.body.blockers).toBe(0);
        expect(res.body.with_dates).toBe(0);
        expect(res.body.monthly_forecast).toEqual({});
        expect(res.body.projected_clear_date).toBeNull();
        expect(res.body.by_vertical).toEqual([]);
    });

    it('returns correct burndown data with mocked DB rows', async () => {
        mockPool.query.mockResolvedValueOnce({
            rows: [
                { hostname: 'srv-001', resolution_date: '2026-06-15', vertical: 'NTS_AEO' },
                { hostname: 'srv-002', resolution_date: '2026-07-01', vertical: 'NTS_AEO' },
                { hostname: 'srv-003', resolution_date: null, vertical: 'SDIT_CISO' },
                { hostname: 'srv-001', resolution_date: '2026-08-01', vertical: 'SDIT_CISO' }, // duplicate hostname
            ],
        });

        const res = await request(server, 'GET', '/api/compliance/vcl-multi/burndown');

        expect(res.statusCode).toBe(200);
        // srv-001 deduplicated: earliest date is 2026-06-15
        expect(res.body.total_non_compliant).toBe(3); // srv-001, srv-002, srv-003
        expect(res.body.blockers).toBe(1); // srv-003
        expect(res.body.with_dates).toBe(2); // srv-001, srv-002
        expect(res.body.monthly_forecast['2026-06']).toBe(1);
        expect(res.body.monthly_forecast['2026-07']).toBe(1);
        expect(res.body.projected_clear_date).toBeNull(); // blockers > 0
    });

    it('returns all-blocker response correctly', async () => {
        mockPool.query.mockResolvedValueOnce({
            rows: [
                { hostname: 'srv-001', resolution_date: null, vertical: 'NTS_AEO' },
                { hostname: 'srv-002', resolution_date: null, vertical: 'SDIT_CISO' },
            ],
        });

        const res = await request(server, 'GET', '/api/compliance/vcl-multi/burndown');

        expect(res.statusCode).toBe(200);
        expect(res.body.total_non_compliant).toBe(2);
        expect(res.body.blockers).toBe(2);
        expect(res.body.with_dates).toBe(0);
        expect(res.body.monthly_forecast).toEqual({});
        expect(res.body.projected_clear_date).toBeNull();
    });

    it('returns 500 on database error', async () => {
        mockPool.query.mockRejectedValueOnce(new Error('Connection refused'));

        const res = await request(server, 'GET', '/api/compliance/vcl-multi/burndown');

        expect(res.statusCode).toBe(500);
        expect(res.body.error).toBe('Database error');
    });

    it('response shape matches API contract', async () => {
        mockPool.query.mockResolvedValueOnce({
            rows: [
                { hostname: 'srv-001', resolution_date: '2026-06-15', vertical: 'NTS_AEO' },
            ],
        });

        const res = await request(server, 'GET', '/api/compliance/vcl-multi/burndown');

        expect(res.statusCode).toBe(200);
        expect(res.body).toHaveProperty('total_non_compliant');
        expect(res.body).toHaveProperty('blockers');
        expect(res.body).toHaveProperty('with_dates');
        expect(res.body).toHaveProperty('monthly_forecast');
        expect(res.body).toHaveProperty('projected_clear_date');
        expect(res.body).toHaveProperty('by_vertical');
        expect(Array.isArray(res.body.by_vertical)).toBe(true);
    });
});

// --- Auth enforcement test ---

describe('GET /burndown — auth enforcement', () => {
    it('returns 401 when auth middleware rejects', async () => {
        // Create a separate app with rejecting auth
        const rejectApp = express();
        rejectApp.use(express.json());

        // Override requireAuth to reject
        jest.resetModules();
        jest.doMock('../middleware/auth', () => ({
            requireAuth: () => (req, res, next) => {
                res.status(401).json({ error: 'Authentication required' });
            },
            requireGroup: () => (req, res, next) => next(),
        }));

        const { createVCLMultiVerticalRouter: createRouter } = require('../routes/vclMultiVertical');
        const mockUpload = { array: () => (req, res, next) => next() };
        const router = createRouter(mockUpload);
        rejectApp.use('/api/compliance/vcl-multi', router);

        const rejectServer = await new Promise((resolve) => {
            const s = rejectApp.listen(0, '127.0.0.1', () => resolve(s));
        });

        try {
            const res = await request(rejectServer, 'GET', '/api/compliance/vcl-multi/burndown');
            expect(res.statusCode).toBe(401);
            expect(res.body.error).toBe('Authentication required');
        } finally {
            await new Promise((resolve) => rejectServer.close(resolve));
        }
    });
});
