import assert from 'node:assert/strict';
import { afterEach, describe, test } from 'node:test';

import type { Pool } from 'pg';

import {
  executeScheduledJob,
  scheduledJobSpecs,
  type ScheduledJobSpec,
} from './scheduler.js';

const originalSecret = process.env.VD_PENDENCIAS_CRON_SECRET;

afterEach(() => {
  if (originalSecret === undefined) delete process.env.VD_PENDENCIAS_CRON_SECRET;
  else process.env.VD_PENDENCIAS_CRON_SECRET = originalSecret;
});

function fakePool(acquired: boolean, queries: string[]): Pool {
  const client = {
    async query(sql: string) {
      queries.push(sql);
      return sql.includes('pg_try_advisory_lock')
        ? { rows: [{ acquired }] }
        : { rows: [{ pg_advisory_unlock: true }] };
    },
    release() {
      queries.push('release');
    },
  };
  return { connect: async () => client } as unknown as Pool;
}

describe('local scheduler', { concurrency: false }, () => {
  test('preserves the three cloud schedules in Africa/Maputo paths', () => {
    assert.deepEqual(
      scheduledJobSpecs.map(({ name, pattern, path }) => ({ name, pattern, path })),
      [
        {
          name: 'tonline-processar-pendencias-vd',
          pattern: '*/2 * * * *',
          path: '/make-server-7249dcd9/vendas-dinheiro/processar-pendencias',
        },
        {
          name: 'tonline-processar-renovacoes-diario',
          pattern: '0 5 * * *',
          path: '/admin-server-7249dcd9/subscricoes/processar-renovacoes',
        },
        {
          name: 'tonline-backup-automatico-diario',
          pattern: '0 20 * * *',
          path: '/admin-server-7249dcd9/backups/automatico-diario',
        },
      ],
    );
  });

  test('calls a job once with its secret while holding a PostgreSQL lock', async () => {
    process.env.VD_PENDENCIAS_CRON_SECRET = 'scheduler-test-secret';
    const spec = scheduledJobSpecs[0] as ScheduledJobSpec;
    const queries: string[] = [];
    let calls = 0;

    await executeScheduledJob(fakePool(true, queries), spec, async (path, init) => {
      calls += 1;
      assert.equal(path, spec.path);
      assert.equal(new Headers(init.headers).get(spec.secretHeader), 'scheduler-test-secret');
      return Response.json({ success: true });
    });

    assert.equal(calls, 1);
    assert.equal(queries.filter((sql) => sql.includes('pg_try_advisory_lock')).length, 1);
    assert.equal(queries.filter((sql) => sql.includes('pg_advisory_unlock')).length, 1);
    assert.equal(queries.at(-1), 'release');
  });

  test('does not call a job when another process owns the lock', async () => {
    process.env.VD_PENDENCIAS_CRON_SECRET = 'scheduler-test-secret';
    const spec = scheduledJobSpecs[0] as ScheduledJobSpec;
    const queries: string[] = [];
    let calls = 0;

    await executeScheduledJob(fakePool(false, queries), spec, () => {
      calls += 1;
      return Response.json({ success: true });
    });

    assert.equal(calls, 0);
    assert.equal(queries.filter((sql) => sql.includes('pg_advisory_unlock')).length, 0);
    assert.equal(queries.at(-1), 'release');
  });
});
