import { describe, it, expect } from 'vitest';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const currentDir = path.dirname(fileURLToPath(import.meta.url));
const migrationFile = '037_schedules-http-timeout.sql';
const migrationPath = path.resolve(
  currentDir,
  `../../src/infra/database/migrations/${migrationFile}`
);

describe('schedules-http-timeout migration', () => {
  const sql = fs.readFileSync(migrationPath, 'utf8');

  it('migration file exists', () => {
    expect(fs.existsSync(migrationPath)).toBe(true);
  });

  // ── idempotency ─────────────────────────────────────────────────────
  it('uses CREATE OR REPLACE FUNCTION (idempotent re-run)', () => {
    expect(sql).toMatch(/CREATE OR REPLACE FUNCTION schedules\.execute_job\(p_job_id UUID\)/i);
  });

  it('does not drop or rename anything', () => {
    expect(sql).not.toMatch(/DROP FUNCTION/i);
    expect(sql).not.toMatch(/DROP TABLE/i);
    expect(sql).not.toMatch(/ALTER TABLE[\s\S]*?RENAME TO/i);
  });

  it('contains no top-level transaction control', () => {
    const outsideDoBlocks = sql.replace(/DO\s*\$\$[\s\S]*?\$\$/g, '');
    expect(outsideDoBlocks).not.toMatch(/^\s*BEGIN\s*;/im);
    expect(outsideDoBlocks).not.toMatch(/^\s*COMMIT\s*;/im);
    expect(outsideDoBlocks).not.toMatch(/^\s*ROLLBACK\s*;/im);
  });

  // ── HTTP timeouts are configured ─────────────────────────────────────
  it('sets CURLOPT_TIMEOUT_MS to 300000 (5min end-to-end timeout)', () => {
    expect(sql).toMatch(/http_set_curlopt\(\s*'CURLOPT_TIMEOUT_MS'\s*,\s*'300000'\s*\)/i);
  });

  it('sets CURLOPT_CONNECTTIMEOUT_MS to 5000 (5s connect timeout)', () => {
    expect(sql).toMatch(/http_set_curlopt\(\s*'CURLOPT_CONNECTTIMEOUT_MS'\s*,\s*'5000'\s*\)/i);
  });

  it('timeouts are set BEFORE the http() call inside execute_job', () => {
    // The two PERFORM http_set_curlopt calls must appear textually before the
    // line that invokes http(v_http_request).
    const setIdx = sql.search(/http_set_curlopt/i);
    const httpIdx = sql.search(/v_http_response\s*:=\s*http\(/i);
    expect(setIdx).toBeGreaterThan(0);
    expect(httpIdx).toBeGreaterThan(setIdx);
  });

  // ── still uses the existing http extension (not pg_net) ──────────────
  it('does NOT introduce pg_net (we are intentionally staying with sync http)', () => {
    expect(sql).not.toMatch(/pg_net/i);
    expect(sql).not.toMatch(/net\.http_post/i);
    expect(sql).not.toMatch(/net\.http_get/i);
    expect(sql).not.toMatch(/net\._http_response/i);
  });

  it('still calls the http extension synchronously', () => {
    expect(sql).toMatch(/v_http_response\s*:=\s*http\s*\(\s*v_http_request\s*\)/i);
  });

  // ── preserves the existing exception path ────────────────────────────
  it('preserves the EXCEPTION WHEN OTHERS log path with status 500', () => {
    expect(sql).toMatch(/EXCEPTION\s+WHEN OTHERS THEN[\s\S]*?log_job_execution[\s\S]*?500/i);
  });

  // ── ordering ─────────────────────────────────────────────────────────
  it('runs after migration 021 (schedules schema)', () => {
    const migrationDir = path.resolve(currentDir, '../../src/infra/database/migrations');
    const migrations = fs
      .readdirSync(migrationDir)
      .filter((f) => f.endsWith('.sql'))
      .sort();

    const idx037 = migrations.findIndex((f) => f === migrationFile);
    const idx021 = migrations.findIndex((f) => f === '021_create-schedules-schema.sql');
    expect(idx021).toBeGreaterThanOrEqual(0);
    expect(idx037).toBeGreaterThan(idx021);
  });
});
