import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import {
  addCorrelationId,
  getCorrelationId,
  resolveCorrelationId,
  MAX_CORRELATION_ID_LENGTH,
} from '../middleware/correlationId.js';
import { createLogger } from '@4runr/shared';

describe('correlationId middleware', () => {
  let mockRequest: { headers: Record<string, string | undefined> };
  let mockReply: { headers: Record<string, string> };
  let setHeader: ReturnType<typeof jest.fn>;

  beforeEach(() => {
    setHeader = jest.fn((name: string, value: string) => {
      mockReply.headers[name] = value;
    });
    mockRequest = { headers: {} };
    mockReply = { headers: {} };
    (mockReply as any).header = setHeader;
  });

  it('generates a correlation ID and sets response header', async () => {
    await addCorrelationId(mockRequest as any, mockReply as any);
    const id = getCorrelationId(mockRequest as any);
    expect(id).toBeDefined();
    expect(id).toMatch(
      /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
    );
    expect(setHeader).toHaveBeenCalledWith('X-Correlation-ID', id);
  });

  it('honors inbound X-Correlation-ID header', async () => {
    const inbound = 'client-trace-abc-123';
    mockRequest.headers['x-correlation-id'] = inbound;
    await addCorrelationId(mockRequest as any, mockReply as any);
    expect(getCorrelationId(mockRequest as any)).toBe(inbound);
    expect(setHeader).toHaveBeenCalledWith('X-Correlation-ID', inbound);
  });

  it('uses first element when header is an array', async () => {
    mockRequest.headers['x-correlation-id'] = ['first-id', 'second-id'] as any;
    await addCorrelationId(mockRequest as any, mockReply as any);
    expect(getCorrelationId(mockRequest as any)).toBe('first-id');
  });

  it('generates UUID when header exceeds max length', async () => {
    const tooLong = 'x'.repeat(MAX_CORRELATION_ID_LENGTH + 1);
    mockRequest.headers['x-correlation-id'] = tooLong;
    await addCorrelationId(mockRequest as any, mockReply as any);
    const id = getCorrelationId(mockRequest as any);
    expect(id).not.toBe(tooLong);
    expect(id).toMatch(
      /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
    );
  });

  it('generates UUID when header is whitespace only', async () => {
    mockRequest.headers['x-correlation-id'] = '   ';
    await addCorrelationId(mockRequest as any, mockReply as any);
    expect(getCorrelationId(mockRequest as any)).toMatch(
      /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
    );
  });
});

describe('resolveCorrelationId', () => {
  it('trims and accepts valid inbound ids', () => {
    expect(resolveCorrelationId('  trace-1  ')).toBe('trace-1');
  });
});

describe('request logger correlation propagation', () => {
  const ORIGINAL_ENV = { ...process.env };
  let consoleSpy: ReturnType<typeof jest.spyOn>;

  beforeEach(() => {
    process.env = { ...ORIGINAL_ENV, NODE_ENV: 'production', LOG_FORMAT: 'json' };
    consoleSpy = jest.spyOn(console, 'info').mockImplementation(() => {});
  });

  afterEach(() => {
    process.env = ORIGINAL_ENV;
    consoleSpy.mockRestore();
  });

  it('includes correlationId in structured log line', () => {
    const cid = '11111111-2222-4333-8444-555555555555';
    const requestLogger = createLogger('Gateway');
    requestLogger.setCorrelationId(cid);
    requestLogger.info('Run created', { runId: 'run-1' });

    expect(consoleSpy).toHaveBeenCalled();
    const line = consoleSpy.mock.calls[0]?.[0] as string;
    const parsed = JSON.parse(line);
    expect(parsed.correlationId).toBe(cid);
    expect(parsed.message).toBe('Run created');
    expect(parsed.data.runId).toBe('run-1');
  });

  it('includes correlationId in log line when LOG_FORMAT=json (dev)', () => {
    process.env.NODE_ENV = 'development';
    process.env.LOG_FORMAT = 'json';
    consoleSpy.mockRestore();
    consoleSpy = jest.spyOn(console, 'info').mockImplementation(() => {});

    const cid = 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee';
    const requestLogger = createLogger('Gateway');
    requestLogger.setCorrelationId(cid);
    requestLogger.info('Probe message');

    const line = consoleSpy.mock.calls[0]?.[0] as string;
    const parsed = JSON.parse(line);
    expect(parsed.correlationId).toBe(cid);
  });
});
