/**
 * v4 schema-level tests for `Llm.poolName` and the dispatcher's
 * `findByPoolName` query semantics. Lives in the db package because it
 * exercises the actual Prisma column + index — the mcpd-side unit tests
 * already cover the dispatcher's behavior with a mocked LlmService.
 */
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import type { PrismaClient } from '@prisma/client';
import { setupTestDb, cleanupTestDb, clearAllTables } from './helpers.js';

/** Re-implementation of the LlmRepository query for direct schema verification. */
function findByPoolName(prisma: PrismaClient, poolName: string) {
  return prisma.llm.findMany({
    where: {
      OR: [
        { poolName },
        { AND: [{ poolName: null }, { name: poolName }] },
      ],
    },
    orderBy: { name: 'asc' },
  });
}

describe('Llm.poolName (v4)', () => {
  let prisma: PrismaClient;

  beforeAll(async () => {
    prisma = await setupTestDb();
  }, 30_000);

  afterAll(async () => {
    await cleanupTestDb();
  });

  beforeEach(async () => {
    await clearAllTables(prisma);
  });

  it('defaults poolName to NULL for freshly inserted rows', async () => {
    const llm = await prisma.llm.create({
      data: { name: 'plain', type: 'openai', model: 'gpt-4o' },
    });
    expect(llm.poolName).toBeNull();
  });

  it('allows multiple rows to share a poolName (the v4 stacking behavior)', async () => {
    await prisma.llm.create({
      data: { name: 'qwen-prod-1', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
    });
    await prisma.llm.create({
      data: { name: 'qwen-prod-2', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
    });
    await prisma.llm.create({
      data: { name: 'qwen-prod-3', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
    });

    const members = await findByPoolName(prisma, 'qwen-pool');
    expect(members.map((m) => m.name).sort()).toEqual(['qwen-prod-1', 'qwen-prod-2', 'qwen-prod-3']);
  });

  it('keeps `name` globally unique even when multiple rows share a poolName', async () => {
    await prisma.llm.create({
      data: { name: 'qwen-prod-1', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
    });
    await expect(
      prisma.llm.create({
        data: { name: 'qwen-prod-1', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
      }),
    ).rejects.toThrow();
  });

  it('falls back to `name` as the effective pool key for solo rows (poolName=NULL)', async () => {
    // Solo row addressable via its own name as a "pool of 1".
    await prisma.llm.create({
      data: { name: 'gpt-4o', type: 'openai', model: 'gpt-4o' },
    });
    const members = await findByPoolName(prisma, 'gpt-4o');
    expect(members.map((m) => m.name)).toEqual(['gpt-4o']);
  });

  it('a solo row with name=X joins the same pool as explicit poolName=X members', async () => {
    // Edge case: an existing solo Llm named "qwen-pool" pre-dates pool
    // adoption, then a publisher registers with poolName=qwen-pool. Both
    // should appear in the dispatcher's candidate list — the effective
    // pool key (poolName ?? name) is "qwen-pool" for each.
    await prisma.llm.create({
      data: { name: 'qwen-pool', type: 'openai', model: 'qwen3-thinking' },
    });
    await prisma.llm.create({
      data: { name: 'qwen-prod-2', type: 'openai', model: 'qwen3-thinking', poolName: 'qwen-pool' },
    });

    const members = await findByPoolName(prisma, 'qwen-pool');
    expect(members.map((m) => m.name).sort()).toEqual(['qwen-pool', 'qwen-prod-2']);
  });

  it('does not match a solo row by `name` when its poolName is set to something else', async () => {
    // Solo with name=foo but poolName=bar should NOT match findByPoolName('foo')
    // — the explicit poolName takes precedence over the name fallback.
    await prisma.llm.create({
      data: { name: 'foo', type: 'openai', model: 'm', poolName: 'bar' },
    });
    const members = await findByPoolName(prisma, 'foo');
    expect(members.map((m) => m.name)).toEqual([]);

    const inBar = await findByPoolName(prisma, 'bar');
    expect(inBar.map((m) => m.name)).toEqual(['foo']);
  });

  it('updates poolName via update() and round-trips correctly', async () => {
    const llm = await prisma.llm.create({
      data: { name: 'qwen-prod-1', type: 'openai', model: 'qwen3-thinking' },
    });
    expect(llm.poolName).toBeNull();

    const updated = await prisma.llm.update({
      where: { id: llm.id },
      data: { poolName: 'qwen-pool' },
    });
    expect(updated.poolName).toBe('qwen-pool');

    // Revert to solo (NULL).
    const reverted = await prisma.llm.update({
      where: { id: llm.id },
      data: { poolName: null },
    });
    expect(reverted.poolName).toBeNull();
  });
});
