/**
 * Unit Tests: POST /api/jira-tickets/:id/queue-items
 *
 * Feature: multi-item-jira-ticket
 *
 * Tests the junction endpoint that links queue items to a Jira ticket.
 * Validates: Requirements 5.3, 6.1, 6.2
 */

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

// Mock the auth middleware so routes don't require real sessions/cookies.
jest.mock('../middleware/auth', () => ({
  requireAuth: () => (req, res, next) => {
    req.user = { id: 1, username: 'test', group: 'Admin' };
    next();
  },
  requireGroup: (...groups) => (req, res, next) => next(),
}));

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

// Mock the jiraApi helper
jest.mock('../helpers/jiraApi', () => ({
  isConfigured: false,
  getRateLimitStatus: jest.fn(() => ({
    burst: { remaining: 60, limit: 60 },
    daily: { remaining: 1440, limit: 1440 },
  })),
}));

const pool = require('../db');
jest.mock('../db', () => ({
  query: jest.fn(),
}));

const createJiraTicketsRouter = require('../routes/jiraTickets');

/**
 * Helper: send an HTTP request to the test server and return { statusCode, body }.
 */
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 raw = Buffer.concat(chunks).toString();
        let parsed;
        try { parsed = JSON.parse(raw); } catch { parsed = raw; }
        resolve({ statusCode: res.statusCode, body: parsed });
      });
    });

    req.on('error', reject);

    if (body) {
      req.write(JSON.stringify(body));
    }
    req.end();
  });
}

describe('POST /api/jira-tickets/:id/queue-items', () => {
  let app;
  let server;

  beforeAll((done) => {
    app = express();
    app.use(express.json());
    app.use('/api/jira-tickets', createJiraTicketsRouter());
    server = app.listen(0, '127.0.0.1', done);
  });

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

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

  // -------------------------------------------------------------------------
  // Validation tests
  // -------------------------------------------------------------------------

  it('returns 400 when queue_item_ids is missing', async () => {
    const res = await request(server, 'POST', '/api/jira-tickets/1/queue-items', {});
    expect(res.statusCode).toBe(400);
    expect(res.body.error).toBe('queue_item_ids must be a non-empty array of integers');
  });

  it('returns 400 when queue_item_ids is an empty array', async () => {
    const res = await request(server, 'POST', '/api/jira-tickets/1/queue-items', {
      queue_item_ids: [],
    });
    expect(res.statusCode).toBe(400);
    expect(res.body.error).toBe('queue_item_ids must be a non-empty array of integers');
  });

  it('returns 400 when queue_item_ids is not an array', async () => {
    const res = await request(server, 'POST', '/api/jira-tickets/1/queue-items', {
      queue_item_ids: 'not-an-array',
    });
    expect(res.statusCode).toBe(400);
    expect(res.body.error).toBe('queue_item_ids must be a non-empty array of integers');
  });

  it('returns 400 when queue_item_ids contains non-integers', async () => {
    const res = await request(server, 'POST', '/api/jira-tickets/1/queue-items', {
      queue_item_ids: [1, 2.5, 3],
    });
    expect(res.statusCode).toBe(400);
    expect(res.body.error).toBe('queue_item_ids must be a non-empty array of integers');
  });

  it('returns 400 when queue_item_ids contains strings', async () => {
    const res = await request(server, 'POST', '/api/jira-tickets/1/queue-items', {
      queue_item_ids: [1, 'abc', 3],
    });
    expect(res.statusCode).toBe(400);
    expect(res.body.error).toBe('queue_item_ids must be a non-empty array of integers');
  });

  // -------------------------------------------------------------------------
  // Ticket existence check
  // -------------------------------------------------------------------------

  it('returns 404 when jira ticket does not exist', async () => {
    pool.query.mockResolvedValueOnce({ rows: [] }); // ticket lookup

    const res = await request(server, 'POST', '/api/jira-tickets/999/queue-items', {
      queue_item_ids: [1, 2, 3],
    });
    expect(res.statusCode).toBe(404);
    expect(res.body.error).toBe('Jira ticket not found');
  });

  // -------------------------------------------------------------------------
  // Queue item existence check
  // -------------------------------------------------------------------------

  it('returns 400 when some queue items do not exist', async () => {
    pool.query
      .mockResolvedValueOnce({ rows: [{ id: 42 }] }) // ticket exists
      .mockResolvedValueOnce({ rows: [{ id: 1 }, { id: 2 }] }); // only 2 of 3 exist

    const res = await request(server, 'POST', '/api/jira-tickets/42/queue-items', {
      queue_item_ids: [1, 2, 3],
    });
    expect(res.statusCode).toBe(400);
    expect(res.body.error).toBe('One or more queue items not found');
  });

  // -------------------------------------------------------------------------
  // Successful linking
  // -------------------------------------------------------------------------

  it('returns 201 with linked_count on success', async () => {
    pool.query
      .mockResolvedValueOnce({ rows: [{ id: 42 }] }) // ticket exists
      .mockResolvedValueOnce({ rows: [{ id: 12 }, { id: 15 }, { id: 18 }] }) // all queue items exist
      .mockResolvedValueOnce({ rowCount: 3 }); // insert result

    const res = await request(server, 'POST', '/api/jira-tickets/42/queue-items', {
      queue_item_ids: [12, 15, 18],
    });
    expect(res.statusCode).toBe(201);
    expect(res.body.message).toBe('Queue items linked to ticket');
    expect(res.body.ticket_id).toBe(42);
    expect(res.body.linked_count).toBe(3);
  });

  it('returns linked_count reflecting ON CONFLICT DO NOTHING (duplicates ignored)', async () => {
    pool.query
      .mockResolvedValueOnce({ rows: [{ id: 42 }] }) // ticket exists
      .mockResolvedValueOnce({ rows: [{ id: 12 }, { id: 15 }] }) // all queue items exist
      .mockResolvedValueOnce({ rowCount: 1 }); // only 1 new row (1 was duplicate)

    const res = await request(server, 'POST', '/api/jira-tickets/42/queue-items', {
      queue_item_ids: [12, 15],
    });
    expect(res.statusCode).toBe(201);
    expect(res.body.linked_count).toBe(1);
  });

  // -------------------------------------------------------------------------
  // Error handling
  // -------------------------------------------------------------------------

  it('returns 500 on database error', async () => {
    pool.query
      .mockResolvedValueOnce({ rows: [{ id: 42 }] }) // ticket exists
      .mockResolvedValueOnce({ rows: [{ id: 12 }] }) // queue items exist
      .mockRejectedValueOnce(new Error('Connection lost')); // insert fails

    const res = await request(server, 'POST', '/api/jira-tickets/42/queue-items', {
      queue_item_ids: [12],
    });
    expect(res.statusCode).toBe(500);
    expect(res.body.error).toContain('Connection lost');
  });
});
