/**
 * Smoke tests for ssl-monitor.js
 * Verifies SSLMonitor loads, exposes the expected interface, can check
 * certificates via mocked TLS, manage state, and persist cache.
 */

jest.mock('tls', () => ({
  connect: jest.fn(),
}));

jest.mock('../src/utilities/fs-helpers', () => ({
  readJsonFile: jest.fn().mockResolvedValue(null),
  writeJsonFile: jest.fn().mockResolvedValue(undefined),
}));

const tls = require('tls');
const fsHelpers = require('../src/utilities/fs-helpers');
const SSLMonitor = require('../src/monitoring/ssl-monitor');

function makeSocket({ cert = null, error = null } = {}) {
  const { EventEmitter } = require('events');
  const socket = new EventEmitter();
  socket.destroy = jest.fn();
  socket.getPeerCertificate = jest.fn(() => cert);
  socket.setTimeout = jest.fn();

  // Simulate 'connect' on next tick (or 'error')
  process.nextTick(() => {
    if (error) socket.emit('error', error);
  });

  return socket;
}

describe('SSLMonitor', () => {
  let monitor;
  const fakeStateManager = {
    read: jest.fn().mockResolvedValue([]),
  };

  beforeEach(() => {
    jest.clearAllMocks();
    fsHelpers.readJsonFile.mockResolvedValue(null);
    fsHelpers.writeJsonFile.mockResolvedValue(undefined);
    fakeStateManager.read.mockResolvedValue([]);

    monitor = new SSLMonitor({
      log: { error: jest.fn(), info: jest.fn(), warn: jest.fn() },
      servicesStateManager: fakeStateManager,
      siteConfig: {},
      buildServiceUrl: id => `https://${id}.sami`,
      notification: null,
    });
  });

  afterEach(() => {
    monitor.stop();
  });

  test('initializes with empty maps and default config', () => {
    expect(monitor.certStatus).toBeInstanceOf(Map);
    expect(monitor.notifiedThresholds).toBeInstanceOf(Map);
    expect(monitor.hostnameToServiceId).toBeInstanceOf(Map);
    expect(monitor.intervalHandle).toBeNull();
    expect(monitor.config.enabled).toBe(true);
    expect(typeof monitor.config.intervalMs).toBe('number');
  });

  test('getConfig returns a copy of the current config', () => {
    const cfg = monitor.getConfig();
    expect(cfg).toEqual(monitor.config);
    cfg.enabled = false;
    // The internal config must not be mutated
    expect(monitor.config.enabled).toBe(true);
  });

  test('updateConfig updates enabled and intervalMs', () => {
    monitor.updateConfig({ enabled: false, intervalMs: 60000 });
    expect(monitor.config.enabled).toBe(false);
    expect(monitor.config.intervalMs).toBe(60000);
  });

  test('updateConfig rejects intervalMs below 60000', () => {
    const original = monitor.config.intervalMs;
    monitor.updateConfig({ intervalMs: 1000 });
    expect(monitor.config.intervalMs).toBe(original);
  });

  test('getStatus returns an empty object when no checks have run', () => {
    expect(monitor.getStatus()).toEqual({});
  });

  test('getServiceCertStatus returns null for unknown service', () => {
    expect(monitor.getServiceCertStatus('unknown-svc')).toBeNull();
  });

  test('checkCert rejects when peer cert is empty', async () => {
    tls.connect.mockImplementation((_opts, onConnect) => {
      const sock = makeSocket({ cert: {} });
      // Simulate immediate 'connect'
      setImmediate(() => onConnect && onConnect());
      return sock;
    });

    await expect(monitor.checkCert('empty.sami')).rejects.toThrow(/No certificate/);
  });

  test('checkCert resolves with cert details on success', async () => {
    const futureDate = new Date(Date.now() + 60 * 24 * 60 * 60 * 1000); // +60d
    const validTo = futureDate.toUTCString();
    tls.connect.mockImplementation((_opts, onConnect) => {
      const sock = makeSocket({
        cert: {
          subject: { CN: 'test.sami' },
          issuer: { O: "Sami's CA" },
          valid_from: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30).toUTCString(),
          valid_to: validTo,
          fingerprint: 'AA:BB:CC',
        },
      });
      setImmediate(() => onConnect && onConnect());
      return sock;
    });

    const result = await monitor.checkCert('test.sami', 443);
    expect(result.hostname).toBe('test.sami');
    expect(result.port).toBe(443);
    expect(result.subject).toBe('test.sami');
    expect(result.daysRemaining).toBeGreaterThan(0);
    expect(typeof result.isExpiring).toBe('boolean');
    expect(typeof result.checkedAt).toBe('string');
  });

  test('checkCert rejects with TLS error event', async () => {
    tls.connect.mockImplementation(() => {
      const sock = makeSocket({ error: new Error('TLS boom') });
      return sock;
    });
    await expect(monitor.checkCert('broken.sami')).rejects.toThrow(/TLS/);
  });

  test('checkAll returns empty status when no services configured', async () => {
    const status = await monitor.checkAll();
    expect(status).toEqual({});
  });

  test('checkAll handles HTTPS services and stores results', async () => {
    fakeStateManager.read.mockResolvedValue([
      { id: 'web', name: 'Web', url: 'https://web.sami' },
    ]);
    const futureDate = new Date(Date.now() + 60 * 24 * 60 * 60 * 1000);
    tls.connect.mockImplementation((_opts, onConnect) => {
      const sock = makeSocket({
        cert: {
          subject: { CN: 'web.sami' },
          issuer: { O: "Sami's CA" },
          valid_from: new Date().toUTCString(),
          valid_to: futureDate.toUTCString(),
          fingerprint: 'AA:BB:CC',
        },
      });
      setImmediate(() => onConnect && onConnect());
      return sock;
    });

    const status = await monitor.checkAll();
    expect(status['web.sami']).toBeDefined();
    expect(status['web.sami'].hostname).toBe('web.sami');
    expect(monitor.getServiceCertStatus('web')).not.toBeNull();
  });

  test('start() schedules periodic checks and stop() clears them', () => {
    jest.useFakeTimers();
    const originalCheckAll = monitor.checkAll.bind(monitor);
    monitor.checkAll = jest.fn().mockResolvedValue(undefined);
    monitor.start(120000);
    expect(monitor.intervalHandle).not.toBeNull();
    monitor.stop();
    expect(monitor.intervalHandle).toBeNull();
    monitor.checkAll = originalCheckAll;
    jest.useRealTimers();
  });

  test('_saveCache and _loadCache round-trip via fs-helpers', async () => {
    await monitor._saveCache();
    expect(fsHelpers.writeJsonFile).toHaveBeenCalled();

    fsHelpers.readJsonFile.mockResolvedValue({
      lastChecked: new Date().toISOString(),
      certs: { 'a.sami': { hostname: 'a.sami', daysRemaining: 30 } },
      hostnameToServiceId: { 'a.sami': 'svc-a' },
    });
    const fresh = new SSLMonitor({
      log: { error: jest.fn(), info: jest.fn(), warn: jest.fn() },
      servicesStateManager: fakeStateManager,
      siteConfig: {},
      buildServiceUrl: id => `https://${id}.sami`,
    });
    await fresh._loadCache();
    expect(fresh.certStatus.get('a.sami')).toBeDefined();
    expect(fresh.hostnameToServiceId.get('a.sami')).toBe('svc-a');
  });
});
