/**
 * Smoke tests for log-digest.js
 * Verifies the singleton LogDigest exposes the expected interface, parses
 * Docker multiplexed log streams, formats digests, and supports on-demand
 * daily digest generation with mocked Docker.
 */

const fsReal = require('fs');
const os = require('os');
const path = require('path');

jest.mock('dockerode', () => {
  const listContainers = jest.fn().mockResolvedValue([]);
  const getContainer = jest.fn(() => ({
    logs: jest.fn().mockResolvedValue(Buffer.from([])),
  }));
  function Docker() {}
  Docker.prototype.listContainers = listContainers;
  Docker.prototype.getContainer = getContainer;
  return Docker;
});

jest.mock('fs', () => {
  const actual = jest.requireActual('fs');
  return {
    ...actual,
    existsSync: jest.fn().mockReturnValue(true),
    mkdirSync: jest.fn(),
  };
});

jest.mock('../src/docker/docker-maintenance', () => ({
  getDiskUsage: jest.fn().mockResolvedValue(null),
}));

const Docker = require('dockerode');
const fs = require('fs');
const logDigest = require('../src/security/log-digest');

describe('LogDigest (singleton)', () => {
  let dockerInstance;
  let tempDir;

  beforeEach(() => {
    // Each test gets a fresh Docker() mock instance
    jest.clearAllMocks();
    fs.existsSync.mockReturnValue(true);
    // Use a real, writable temp directory so writeFile inside generateDailyDigest
    // does not blow up. Each test gets a fresh dir to avoid cross-test pollution.
    tempDir = fsReal.mkdtempSync(path.join(os.tmpdir(), 'dc-digest-test-'));
    logDigest.hourlySummaries = [];
    logDigest.lastCollect = null;
    logDigest.running = false;
    logDigest.digestDir = null;
    if (logDigest.collectInterval) {
      clearInterval(logDigest.collectInterval);
      logDigest.collectInterval = null;
    }
    if (logDigest.digestTimeout) {
      clearTimeout(logDigest.digestTimeout);
      logDigest.digestTimeout = null;
    }
    dockerInstance = new Docker();
  });

  afterEach(() => {
    logDigest.stop();
    if (tempDir && fsReal.existsSync(tempDir)) {
      fsReal.rmSync(tempDir, { recursive: true, force: true });
    }
  });

  test('is an EventEmitter and exposes the documented API', () => {
    expect(typeof logDigest.on).toBe('function');
    expect(typeof logDigest.emit).toBe('function');
    expect(typeof logDigest.start).toBe('function');
    expect(typeof logDigest.stop).toBe('function');
    expect(typeof logDigest.generateDailyDigest).toBe('function');
    expect(typeof logDigest.getLatestDigest).toBe('function');
    expect(typeof logDigest.getDigestByDate).toBe('function');
    expect(typeof logDigest.getDigestText).toBe('function');
    expect(typeof logDigest.listDigests).toBe('function');
    expect(typeof logDigest.getLiveData).toBe('function');
    expect(typeof logDigest.getStatus).toBe('function');
  });

  test('getStatus returns current state', () => {
    const status = logDigest.getStatus();
    expect(status).toEqual({
      running: false,
      lastCollect: null,
      hourlySummaries: 0,
      digestDir: null,
    });
  });

  test('start sets running and digestDir', () => {
    logDigest.start(tempDir);
    expect(logDigest.running).toBe(true);
    expect(logDigest.digestDir).toBe(tempDir);
  });

  test('start is idempotent — second call does nothing new', () => {
    logDigest.start(tempDir);
    const firstInterval = logDigest.collectInterval;
    logDigest.start(tempDir);
    expect(logDigest.collectInterval).toBe(firstInterval);
  });

  test('_parseDockerLogs decodes multiplexed log frames into lines', () => {
    // Stream type byte: 0=stdin, 1=stdout, 2=stderr
    // Header: [type, 0, 0, 0, size-BE-uint32]
    function frame(streamType, text) {
      const buf = Buffer.from(text, 'utf8');
      const header = Buffer.alloc(8);
      header[0] = streamType;
      header.writeUInt32BE(buf.length, 4);
      return Buffer.concat([header, buf]);
    }

    const multiplexed = Buffer.concat([
      frame(1, 'hello world\n'),
      frame(2, '2026-03-13T12:00:00.000Z an error happened\n'),
    ]);

    const lines = logDigest._parseDockerLogs(multiplexed);
    expect(lines).toHaveLength(2);
    expect(lines[0]).toEqual({
      stream: 'stdout',
      text: 'hello world',
      timestamp: null,
    });
    expect(lines[1].stream).toBe('stderr');
    expect(lines[1].text).toBe('an error happened');
    expect(lines[1].timestamp).toBe('2026-03-13T12:00:00');
  });

  test('generateDailyDigest with empty summaries produces minimal digest', async () => {
    logDigest.start(tempDir);
    const digest = await logDigest.generateDailyDigest('2099-01-01');
    expect(digest.date).toBe('2099-01-01');
    expect(digest.services).toEqual({});
    expect(digest.summary.totalServices).toBe(0);
    expect(digest.summary.totalErrors).toBe(0);
    expect(Array.isArray(digest.notableEvents)).toBe(true);

    // Confirm the file was actually written
    const writtenPath = path.join(tempDir, 'digest-2099-01-01.log');
    expect(fsReal.existsSync(writtenPath)).toBe(true);
    const jsonPath = path.join(tempDir, 'digest-2099-01-01.json');
    expect(fsReal.existsSync(jsonPath)).toBe(true);
  });

  test('getLiveData returns shape with date, hoursCollected, services', () => {
    const data = logDigest.getLiveData();
    expect(data).toHaveProperty('date');
    expect(data).toHaveProperty('hoursCollected');
    expect(data).toHaveProperty('services');
    expect(data).toHaveProperty('lastCollect');
  });

  test('getLatestDigest returns null when digestDir is null', async () => {
    logDigest.digestDir = null;
    const result = await logDigest.getLatestDigest();
    expect(result).toBeNull();
  });

  test('getDigestByDate returns null when no file exists', async () => {
    logDigest.digestDir = '/nonexistent/path';
    const result = await logDigest.getDigestByDate('2020-01-01');
    expect(result).toBeNull();
  });

  test('listDigests returns empty array when digestDir is null', async () => {
    logDigest.digestDir = null;
    const result = await logDigest.listDigests();
    expect(result).toEqual([]);
  });

  test('stop clears intervals and timeouts', () => {
    logDigest.start(tempDir);
    logDigest.stop();
    expect(logDigest.running).toBe(false);
    expect(logDigest.collectInterval).toBeNull();
    expect(logDigest.digestTimeout).toBeNull();
  });
});
