// resource-monitor.js creates a Docker instance at module level.
// On test machines without Docker, the constructor reads from non-existent files (returns defaults).

const resourceMonitor = require('../resource-monitor');

beforeEach(() => {
  // Reset singleton state
  resourceMonitor.stats = new Map();
  resourceMonitor.alerts = new Map();
  resourceMonitor.lastAlerts = new Map();
  resourceMonitor.monitoring = false;
  if (resourceMonitor.monitoringInterval) {
    clearInterval(resourceMonitor.monitoringInterval);
    resourceMonitor.monitoringInterval = null;
  }
});

afterAll(() => {
  resourceMonitor.stop();
});

// Helper: create a stat entry
function makeStat(cpu = 10, memory = 50, timestamp = new Date().toISOString()) {
  return {
    timestamp,
    cpu: { percent: cpu, usage: cpu * 1000 },
    memory: { usage: memory * 1024 * 1024, limit: 1024 * 1024 * 1024, percent: memory, usageMB: memory, limitMB: 1024 },
    network: { rxBytes: 0, txBytes: 0, rxMB: 0, txMB: 0 },
    disk: { readBytes: 0, writeBytes: 0, readMB: 0, writeMB: 0 },
    pids: 5
  };
}

describe('recordStats', () => {
  test('creates new entry for unknown container', () => {
    resourceMonitor.recordStats('c1', '/my-app', makeStat());
    expect(resourceMonitor.stats.has('c1')).toBe(true);
    expect(resourceMonitor.stats.get('c1').history).toHaveLength(1);
  });

  test('appends to existing history', () => {
    resourceMonitor.recordStats('c1', '/my-app', makeStat());
    resourceMonitor.recordStats('c1', '/my-app', makeStat());
    expect(resourceMonitor.stats.get('c1').history).toHaveLength(2);
  });

  test('updates container name', () => {
    resourceMonitor.recordStats('c1', '/old-name', makeStat());
    resourceMonitor.recordStats('c1', '/new-name', makeStat());
    expect(resourceMonitor.stats.get('c1').name).toBe('/new-name');
  });
});

describe('getCurrentStats', () => {
  test('returns null for unknown container', () => {
    expect(resourceMonitor.getCurrentStats('nonexistent')).toBeNull();
  });

  test('returns latest history entry', () => {
    const stat1 = makeStat(10);
    const stat2 = makeStat(50);
    resourceMonitor.recordStats('c1', '/app', stat1);
    resourceMonitor.recordStats('c1', '/app', stat2);
    expect(resourceMonitor.getCurrentStats('c1').cpu.percent).toBe(50);
  });
});

describe('getHistoricalStats', () => {
  test('returns empty array for unknown container', () => {
    expect(resourceMonitor.getHistoricalStats('nonexistent')).toEqual([]);
  });

  test('filters by time window', () => {
    const recent = makeStat(10, 50, new Date().toISOString());
    const old = makeStat(10, 50, new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString());
    resourceMonitor.stats.set('c1', { name: '/app', history: [old, recent] });
    const result = resourceMonitor.getHistoricalStats('c1', 24);
    expect(result).toHaveLength(1);
    expect(result[0]).toBe(recent);
  });
});

describe('getAggregatedStats', () => {
  test('returns null for unknown container', () => {
    expect(resourceMonitor.getAggregatedStats('nonexistent')).toBeNull();
  });

  test('returns null when no recent history', () => {
    const old = makeStat(10, 50, new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString());
    resourceMonitor.stats.set('c1', { name: '/app', history: [old] });
    expect(resourceMonitor.getAggregatedStats('c1', 24)).toBeNull();
  });

  test('calculates correct avg/min/max for CPU', () => {
    const now = new Date().toISOString();
    resourceMonitor.stats.set('c1', {
      name: '/app',
      history: [makeStat(10, 50, now), makeStat(30, 50, now), makeStat(20, 50, now)]
    });
    const agg = resourceMonitor.getAggregatedStats('c1', 24);
    expect(agg.cpu.avg).toBe(20);
    expect(agg.cpu.min).toBe(10);
    expect(agg.cpu.max).toBe(30);
  });

  test('calculates correct avg/min/max for memory', () => {
    const now = new Date().toISOString();
    resourceMonitor.stats.set('c1', {
      name: '/app',
      history: [makeStat(10, 40, now), makeStat(10, 60, now), makeStat(10, 80, now)]
    });
    const agg = resourceMonitor.getAggregatedStats('c1', 24);
    expect(agg.memory.avg).toBe(60);
    expect(agg.memory.min).toBe(40);
    expect(agg.memory.max).toBe(80);
  });

  test('includes dataPoints and timeRange', () => {
    const now = new Date().toISOString();
    resourceMonitor.stats.set('c1', { name: '/app', history: [makeStat(10, 50, now)] });
    const agg = resourceMonitor.getAggregatedStats('c1', 24);
    expect(agg.dataPoints).toBe(1);
    expect(agg.timeRange).toBe(24);
  });
});

describe('checkAlerts', () => {
  test('does nothing when alert config is missing', () => {
    const handler = jest.fn();
    resourceMonitor.on('alert', handler);
    resourceMonitor.checkAlerts('c1', '/app', makeStat(99));
    expect(handler).not.toHaveBeenCalled();
    resourceMonitor.removeListener('alert', handler);
  });

  test('does nothing when alerts are disabled', () => {
    resourceMonitor.alerts.set('c1', { enabled: false, cpuThreshold: 50 });
    const handler = jest.fn();
    resourceMonitor.on('alert', handler);
    resourceMonitor.checkAlerts('c1', '/app', makeStat(99));
    expect(handler).not.toHaveBeenCalled();
    resourceMonitor.removeListener('alert', handler);
  });

  test('triggers CPU alert when threshold exceeded', () => {
    resourceMonitor.alerts.set('c1', { enabled: true, cpuThreshold: 50, cooldownMinutes: 0 });
    const handler = jest.fn();
    resourceMonitor.on('alert', handler);
    resourceMonitor.checkAlerts('c1', '/app', makeStat(75));
    expect(handler).toHaveBeenCalled();
    const alertData = handler.mock.calls[0][0];
    expect(alertData.alerts[0].type).toBe('cpu');
    resourceMonitor.removeListener('alert', handler);
  });

  test('triggers memory alert when threshold exceeded', () => {
    resourceMonitor.alerts.set('c1', { enabled: true, memoryThreshold: 70, cooldownMinutes: 0 });
    const handler = jest.fn();
    resourceMonitor.on('alert', handler);
    resourceMonitor.checkAlerts('c1', '/app', makeStat(10, 80));
    expect(handler).toHaveBeenCalled();
    const alertData = handler.mock.calls[0][0];
    expect(alertData.alerts[0].type).toBe('memory');
    resourceMonitor.removeListener('alert', handler);
  });

  test('respects cooldown period', () => {
    resourceMonitor.alerts.set('c1', { enabled: true, cpuThreshold: 50, cooldownMinutes: 15 });
    resourceMonitor.lastAlerts.set('c1', Date.now()); // Just alerted
    const handler = jest.fn();
    resourceMonitor.on('alert', handler);
    resourceMonitor.checkAlerts('c1', '/app', makeStat(99));
    expect(handler).not.toHaveBeenCalled();
    resourceMonitor.removeListener('alert', handler);
  });

  test('does not trigger when below threshold', () => {
    resourceMonitor.alerts.set('c1', { enabled: true, cpuThreshold: 90, cooldownMinutes: 0 });
    const handler = jest.fn();
    resourceMonitor.on('alert', handler);
    resourceMonitor.checkAlerts('c1', '/app', makeStat(50));
    expect(handler).not.toHaveBeenCalled();
    resourceMonitor.removeListener('alert', handler);
  });
});

describe('setAlertConfig / getAlertConfig / removeAlertConfig', () => {
  test('stores alert config', () => {
    resourceMonitor.setAlertConfig('c1', { cpuThreshold: 80 });
    expect(resourceMonitor.alerts.has('c1')).toBe(true);
  });

  test('retrieves stored config', () => {
    resourceMonitor.setAlertConfig('c1', { cpuThreshold: 80 });
    const config = resourceMonitor.getAlertConfig('c1');
    expect(config.cpuThreshold).toBe(80);
  });

  test('returns null for non-existent config', () => {
    expect(resourceMonitor.getAlertConfig('nonexistent')).toBeNull();
  });

  test('removes config and last alert', () => {
    resourceMonitor.setAlertConfig('c1', { cpuThreshold: 80 });
    resourceMonitor.lastAlerts.set('c1', Date.now());
    resourceMonitor.removeAlertConfig('c1');
    expect(resourceMonitor.alerts.has('c1')).toBe(false);
    expect(resourceMonitor.lastAlerts.has('c1')).toBe(false);
  });
});

describe('getAllStats', () => {
  test('returns empty object when no stats', () => {
    expect(resourceMonitor.getAllStats()).toEqual({});
  });

  test('includes current and aggregated for each container', () => {
    const now = new Date().toISOString();
    resourceMonitor.stats.set('c1', { name: '/app', history: [makeStat(10, 50, now)] });
    const all = resourceMonitor.getAllStats();
    expect(all['c1']).toBeDefined();
    expect(all['c1'].name).toBe('/app');
    expect(all['c1'].current).toBeDefined();
    expect(all['c1'].aggregated).toBeDefined();
  });
});

describe('exportStats / importStats', () => {
  test('export returns object with stats and alerts', () => {
    const now = new Date().toISOString();
    resourceMonitor.stats.set('c1', { name: '/app', history: [makeStat(10, 50, now)] });
    resourceMonitor.alerts.set('c1', { enabled: true, cpuThreshold: 80 });
    const exported = resourceMonitor.exportStats();
    expect(exported.stats).toBeDefined();
    expect(exported.alerts).toBeDefined();
    expect(exported.exportedAt).toBeDefined();
  });

  test('import restores stats from backup', () => {
    const backup = {
      stats: { 'c1': { name: '/app', history: [makeStat()] } },
      alerts: { 'c1': { enabled: true, cpuThreshold: 80 } }
    };
    resourceMonitor.importStats(backup);
    expect(resourceMonitor.stats.has('c1')).toBe(true);
    expect(resourceMonitor.alerts.has('c1')).toBe(true);
  });
});

describe('cleanupOldStats', () => {
  test('removes entries older than retention period', () => {
    const old = makeStat(10, 50, new Date(Date.now() - 200 * 60 * 60 * 1000).toISOString());
    const recent = makeStat(10, 50, new Date().toISOString());
    resourceMonitor.stats.set('c1', { name: '/app', history: [old, recent] });
    resourceMonitor.cleanupOldStats();
    expect(resourceMonitor.stats.get('c1').history).toHaveLength(1);
  });

  test('deletes container entirely when no recent data', () => {
    const old = makeStat(10, 50, new Date(Date.now() - 200 * 60 * 60 * 1000).toISOString());
    resourceMonitor.stats.set('c1', { name: '/app', history: [old] });
    resourceMonitor.cleanupOldStats();
    expect(resourceMonitor.stats.has('c1')).toBe(false);
  });
});

describe('start / stop', () => {
  test('start sets monitoring flag', () => {
    jest.useFakeTimers();
    resourceMonitor.start();
    expect(resourceMonitor.monitoring).toBe(true);
    resourceMonitor.stop();
    jest.useRealTimers();
  });

  test('stop clears interval', () => {
    jest.useFakeTimers();
    resourceMonitor.start();
    resourceMonitor.stop();
    expect(resourceMonitor.monitoring).toBe(false);
    expect(resourceMonitor.monitoringInterval).toBeNull();
    jest.useRealTimers();
  });

  test('start is idempotent', () => {
    jest.useFakeTimers();
    resourceMonitor.start();
    const first = resourceMonitor.monitoringInterval;
    resourceMonitor.start();
    expect(resourceMonitor.monitoringInterval).toBe(first);
    resourceMonitor.stop();
    jest.useRealTimers();
  });
});
