import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { DbHandle } from '@mosaicstack/db';

// Mock @mosaicstack/db before importing the adapter
vi.mock('@mosaicstack/db', async (importOriginal) => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const actual = await importOriginal<Record<string, any>>();
  return {
    ...actual,
    createDb: vi.fn(),
    runMigrations: vi.fn().mockResolvedValue(undefined),
  };
});

import { createDb, runMigrations } from '@mosaicstack/db';
import { PostgresAdapter } from './postgres.js';

describe('PostgresAdapter — vector extension gating', () => {
  let mockExecute: ReturnType<typeof vi.fn>;
  let mockDb: { execute: ReturnType<typeof vi.fn> };
  let mockHandle: Pick<DbHandle, 'close'> & { db: typeof mockDb };

  beforeEach(() => {
    vi.clearAllMocks();
    mockExecute = vi.fn().mockResolvedValue(undefined);
    mockDb = { execute: mockExecute };
    mockHandle = { db: mockDb, close: vi.fn().mockResolvedValue(undefined) };
    vi.mocked(createDb).mockReturnValue(mockHandle as unknown as DbHandle);
  });

  it('calls db.execute with CREATE EXTENSION IF NOT EXISTS vector when enableVector=true', async () => {
    const adapter = new PostgresAdapter({
      type: 'postgres',
      url: 'postgresql://test:test@localhost:5432/test',
      enableVector: true,
    });

    await adapter.migrate();

    // Should have called execute
    expect(mockExecute).toHaveBeenCalledTimes(1);

    // Verify the SQL contains the extension creation statement.
    // Prefer Drizzle's public toSQL() API; fall back to queryChunks if unavailable.
    // NOTE: queryChunks is an undocumented Drizzle internal (drizzle-orm ^0.45.x).
    // toSQL() was not present on the raw sql`` result in this version — if a future
    // Drizzle upgrade adds it, remove the fallback path and delete this comment.
    const sqlObj = mockExecute.mock.calls[0]![0] as {
      toSQL?: () => { sql: string; params: unknown[] };
      queryChunks?: Array<{ value: string[] }>;
    };
    const sqlText = sqlObj.toSQL
      ? sqlObj.toSQL().sql.toLowerCase()
      : (sqlObj.queryChunks ?? [])
          .flatMap((chunk) => chunk.value)
          .join('')
          .toLowerCase();
    expect(sqlText).toContain('create extension if not exists vector');
  });

  it('does NOT call db.execute for extension when enableVector is false', async () => {
    const adapter = new PostgresAdapter({
      type: 'postgres',
      url: 'postgresql://test:test@localhost:5432/test',
      enableVector: false,
    });

    await adapter.migrate();

    expect(mockExecute).not.toHaveBeenCalled();
    expect(vi.mocked(runMigrations)).toHaveBeenCalledOnce();
  });

  it('does NOT call db.execute for extension when enableVector is unset', async () => {
    const adapter = new PostgresAdapter({
      type: 'postgres',
      url: 'postgresql://test:test@localhost:5432/test',
    });

    await adapter.migrate();

    expect(mockExecute).not.toHaveBeenCalled();
    expect(vi.mocked(runMigrations)).toHaveBeenCalledOnce();
  });

  it('calls runMigrations after the extension is created', async () => {
    const callOrder: string[] = [];
    mockExecute.mockImplementation(() => {
      callOrder.push('execute');
      return Promise.resolve(undefined);
    });
    vi.mocked(runMigrations).mockImplementation(() => {
      callOrder.push('runMigrations');
      return Promise.resolve();
    });

    const adapter = new PostgresAdapter({
      type: 'postgres',
      url: 'postgresql://test:test@localhost:5432/test',
      enableVector: true,
    });

    await adapter.migrate();

    expect(callOrder).toEqual(['execute', 'runMigrations']);
  });
});
