import { describe, expect, it } from '@jest/globals';
import { ChildProcess, spawn } from 'node:child_process';
import http from 'node:http';
import path from 'node:path';

const { getWorkerHeartbeatKey, isHeartbeatFresh } = require('../../../healthcheck.cjs');

function waitForExit(child: ChildProcess): Promise<number | null> {
  return new Promise((resolve, reject) => {
    child.once('error', reject);
    child.once('exit', resolve);
  });
}

describe('API healthcheck', () => {
  it('uses readiness and fails when dependencies are unavailable', async () => {
    const requestedPaths: string[] = [];
    const server = http.createServer((request, response) => {
      requestedPaths.push(request.url || '');
      response.writeHead(request.url === '/health/ready' ? 503 : 200).end();
    });

    await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
    const address = server.address();
    if (!address || typeof address === 'string') {
      throw new Error('Test server did not bind to a TCP port');
    }

    const child = spawn(process.execPath, [path.resolve(__dirname, '../../../healthcheck.cjs'), 'api'], {
      env: { ...process.env, PORT: String(address.port) },
      stdio: 'ignore',
    });

    await expect(waitForExit(child)).resolves.toBe(1);
    expect(requestedPaths).toEqual(['/health/ready']);

    await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  });
});

describe('worker healthcheck', () => {
  it('derives an instance-specific key from HOSTNAME', () => {
    expect(getWorkerHeartbeatKey('container-a')).toBe('worker:health:container-a');
    expect(getWorkerHeartbeatKey('container-b')).toBe('worker:health:container-b');
  });

  it('uses WORKER_ID when healthcheck execution has no HOSTNAME', () => {
    const originalWorkerId = process.env.WORKER_ID;
    const originalHostname = process.env.HOSTNAME;
    process.env.WORKER_ID = 'presszone-worker';
    delete process.env.HOSTNAME;

    try {
      expect(getWorkerHeartbeatKey()).toBe('worker:health:presszone-worker');
    } finally {
      if (originalWorkerId === undefined) {
        delete process.env.WORKER_ID;
      } else {
        process.env.WORKER_ID = originalWorkerId;
      }
      if (originalHostname === undefined) {
        delete process.env.HOSTNAME;
      } else {
        process.env.HOSTNAME = originalHostname;
      }
    }
  });

  it('rejects absent, malformed, and stale heartbeats', () => {
    const now = 10_000;

    expect(isHeartbeatFresh(null, now, 5_000)).toBe(false);
    expect(isHeartbeatFresh('not-a-timestamp', now, 5_000)).toBe(false);
    expect(isHeartbeatFresh('4999', now, 5_000)).toBe(false);
  });

  it('accepts a recent heartbeat', () => {
    expect(isHeartbeatFresh('9000', 10_000, 5_000)).toBe(true);
  });
});
