import type { Pool } from 'pg';
import { beforeEach, describe, expect, it, vi } from 'vitest';

const { mockLogger } = vi.hoisted(() => ({
  mockLogger: { error: vi.fn(), info: vi.fn(), warn: vi.fn(), debug: vi.fn() },
}));

vi.mock('../../src/utils/logger', () => ({ default: mockLogger }));

import { withPaymentSessionAdvisoryLock } from '../../src/services/payments/payments-advisory-lock';

describe('withPaymentSessionAdvisoryLock', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('releases the client normally after a successful unlock', async () => {
    const client = {
      query: vi.fn().mockResolvedValue({ rows: [] }),
      release: vi.fn(),
    };
    const pool = {
      connect: vi.fn().mockResolvedValue(client),
    } as unknown as Pool;

    await expect(
      withPaymentSessionAdvisoryLock(pool, 'payments_environment_test', async () => 'ok')
    ).resolves.toBe('ok');

    expect(client.query).toHaveBeenNthCalledWith(1, 'SELECT pg_advisory_lock(hashtext($1))', [
      'payments_environment_test',
    ]);
    expect(client.query).toHaveBeenNthCalledWith(2, 'SELECT pg_advisory_unlock(hashtext($1))', [
      'payments_environment_test',
    ]);
    expect(client.release).toHaveBeenCalledWith();
  });

  it('destroys the pooled client if unlock fails', async () => {
    const unlockError = new Error('unlock failed');
    const client = {
      query: vi.fn().mockResolvedValueOnce({ rows: [] }).mockRejectedValueOnce(unlockError),
      release: vi.fn(),
    };
    const pool = {
      connect: vi.fn().mockResolvedValue(client),
    } as unknown as Pool;

    await expect(
      withPaymentSessionAdvisoryLock(pool, 'payments_environment_test', async () => 'ok')
    ).rejects.toThrow('unlock failed');

    expect(client.query).toHaveBeenNthCalledWith(1, 'SELECT pg_advisory_lock(hashtext($1))', [
      'payments_environment_test',
    ]);
    expect(client.query).toHaveBeenNthCalledWith(2, 'SELECT pg_advisory_unlock(hashtext($1))', [
      'payments_environment_test',
    ]);
    expect(client.release).toHaveBeenCalledWith(true);
    expect(mockLogger.error).toHaveBeenCalledWith(
      'Failed to release payments advisory lock',
      expect.objectContaining({
        lockName: 'payments_environment_test',
        mode: 'exclusive',
        error: 'unlock failed',
      })
    );
  });

  it('preserves the original task error when unlock also fails', async () => {
    const taskError = new Error('task failed');
    const unlockError = new Error('unlock failed');
    const client = {
      query: vi.fn().mockResolvedValueOnce({ rows: [] }).mockRejectedValueOnce(unlockError),
      release: vi.fn(),
    };
    const pool = {
      connect: vi.fn().mockResolvedValue(client),
    } as unknown as Pool;

    await expect(
      withPaymentSessionAdvisoryLock(pool, 'payments_environment_test', async () => {
        throw taskError;
      })
    ).rejects.toThrow('task failed');

    expect(client.release).toHaveBeenCalledWith(true);
    expect(mockLogger.error).toHaveBeenCalledWith(
      'Failed to release payments advisory lock',
      expect.objectContaining({
        lockName: 'payments_environment_test',
        mode: 'exclusive',
        error: 'unlock failed',
        originalError: 'task failed',
      })
    );
  });
});
