import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ERROR_CODES } from '@insforge/shared-schemas';

const {
  connectMock,
  parseSQLStatementsMock,
  analyzeQueryMock,
  initSqlParserMock,
  checkSqlExecutionGuardsMock,
} = vi.hoisted(() => ({
  connectMock: vi.fn(),
  parseSQLStatementsMock: vi.fn((sql: string) => [sql]),
  analyzeQueryMock: vi.fn(() => []),
  initSqlParserMock: vi.fn(async () => {}),
  checkSqlExecutionGuardsMock: vi.fn((query: string) =>
    query.includes('RESET ROLE')
      ? 'Changing SQL execution role or session authorization is not allowed.'
      : null
  ),
}));

vi.mock('../../src/infra/database/database.manager', () => ({
  DatabaseManager: {
    getInstance: vi.fn(() => ({
      getPool: vi.fn(() => ({
        connect: connectMock,
      })),
    })),
    clearColumnTypeCache: vi.fn(),
  },
}));

vi.mock('../../src/utils/sql-parser', () => ({
  parseSQLStatements: parseSQLStatementsMock,
  analyzeQuery: analyzeQueryMock,
  initSqlParser: initSqlParserMock,
  checkSqlExecutionGuards: checkSqlExecutionGuardsMock,
}));

import { DatabaseMigrationService } from '../../src/services/database/database-migration.service';

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

  it('blocks execution context changes before opening a database transaction', async () => {
    const service = DatabaseMigrationService.getInstance();

    await expect(
      service.createMigration({
        version: '202605020001',
        name: 'role-reset',
        sql: 'RESET ROLE',
      })
    ).rejects.toMatchObject({
      statusCode: 403,
      code: ERROR_CODES.FORBIDDEN,
    });

    expect(checkSqlExecutionGuardsMock).toHaveBeenCalledWith('RESET ROLE');
    expect(connectMock).not.toHaveBeenCalled();
  });

  it('reloads only the PostgREST schema cache after an allowed migration', async () => {
    const queryMock = vi
      .fn()
      .mockResolvedValueOnce({}) // BEGIN
      .mockResolvedValueOnce({}) // advisory lock
      .mockResolvedValueOnce({}) // search_path
      .mockResolvedValueOnce({ rows: [] }) // latest version
      .mockResolvedValueOnce({}) // SET LOCAL ROLE project_admin
      .mockResolvedValueOnce({}) // set local request.jwt.claims
      .mockResolvedValueOnce({}) // execute migration SQL
      .mockResolvedValueOnce({}) // RESET ROLE
      .mockResolvedValueOnce({}) // reset request.jwt.claims
      .mockResolvedValueOnce({
        rows: [
          {
            version: '202605020002',
            name: 'create-products',
            statements: ['CREATE TABLE public.products (id uuid)'],
            createdAt: '2026-05-02T00:00:00.000Z',
          },
        ],
      }) // insert custom_migrations row
      .mockResolvedValueOnce({}) // reload schema
      .mockResolvedValueOnce({}); // COMMIT

    connectMock.mockResolvedValue({
      query: queryMock,
      release: vi.fn(),
    });

    const service = DatabaseMigrationService.getInstance();
    const result = await service.createMigration({
      version: '202605020002',
      name: 'create-products',
      sql: 'CREATE TABLE public.products (id uuid)',
    });

    expect(result.migration.version).toBe('202605020002');
    expect(queryMock).toHaveBeenCalledWith('SET LOCAL ROLE project_admin');
    expect(queryMock).toHaveBeenCalledWith('SELECT set_config($1, $2, $3)', [
      'request.jwt.claims',
      JSON.stringify({ role: 'project_admin' }),
      true,
    ]);
    expect(queryMock).toHaveBeenCalledWith('RESET ROLE');
    expect(queryMock).toHaveBeenCalledWith(`NOTIFY pgrst, 'reload schema';`);
    expect(queryMock).not.toHaveBeenCalledWith(`NOTIFY pgrst, 'reload config';`);
  });
});
