import { describe, it, expect, vi, beforeEach } from 'vitest';
import { bootstrapSystemProject, SYSTEM_PROJECT_NAME, getSystemPromptNames } from '../src/bootstrap/system-project.js';
import type { PrismaClient } from '@prisma/client';

function mockPrisma(): PrismaClient {
  const prompts = new Map<string, { id: string; name: string; projectId: string }>();
  let promptIdCounter = 1;

  return {
    user: {
      upsert: vi.fn(async () => ({
        id: 'sys-user-id',
        email: 'system@mcpctl.local',
        name: 'System',
      })),
    },
    project: {
      upsert: vi.fn(async (args: { where: { name: string }; create: Record<string, unknown>; update: Record<string, unknown> }) => ({
        id: 'sys-proj-id',
        name: args.where.name,
        ...args.create,
      })),
    },
    prompt: {
      findFirst: vi.fn(async (args: { where: { name: string; projectId: string } }) => {
        return prompts.get(`${args.where.projectId}:${args.where.name}`) ?? null;
      }),
      create: vi.fn(async (args: { data: { name: string; content: string; priority: number; projectId: string } }) => {
        const id = `prompt-${promptIdCounter++}`;
        const prompt = { id, ...args.data };
        prompts.set(`${args.data.projectId}:${args.data.name}`, prompt);
        return prompt;
      }),
    },
  } as unknown as PrismaClient;
}

describe('bootstrapSystemProject', () => {
  let prisma: PrismaClient;

  beforeEach(() => {
    prisma = mockPrisma();
  });

  it('creates a system user via upsert', async () => {
    await bootstrapSystemProject(prisma);

    expect(prisma.user.upsert).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { email: 'system@mcpctl.local' },
        create: expect.objectContaining({
          email: 'system@mcpctl.local',
          name: 'System',
        }),
      }),
    );
  });

  it('creates the mcpctl-system project via upsert', async () => {
    await bootstrapSystemProject(prisma);

    expect(prisma.project.upsert).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { name: SYSTEM_PROJECT_NAME },
        create: expect.objectContaining({
          name: SYSTEM_PROJECT_NAME,
          ownerId: 'sys-user-id',
          gated: false,
        }),
        update: {},
      }),
    );
  });

  it('creates all system prompts', async () => {
    await bootstrapSystemProject(prisma);

    const expectedNames = getSystemPromptNames();
    expect(expectedNames.length).toBeGreaterThanOrEqual(4);

    for (const name of expectedNames) {
      expect(prisma.prompt.findFirst).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { name, projectId: 'sys-proj-id' },
        }),
      );
    }

    expect(prisma.prompt.create).toHaveBeenCalledTimes(expectedNames.length);
  });

  it('creates system prompts with expected priorities', async () => {
    await bootstrapSystemProject(prisma);

    const createCalls = vi.mocked(prisma.prompt.create).mock.calls;
    for (const call of createCalls) {
      const data = (call[0] as { data: { name: string; priority: number } }).data;
      // Gate prompts have priority 10, LLM pipeline prompts have priority 5
      if (data.name.startsWith('gate-') || data.name === 'session-greeting') {
        expect(data.priority).toBe(10);
      } else {
        expect(data.priority).toBe(5);
      }
    }
  });

  it('does not re-create existing prompts (idempotent)', async () => {
    // First call creates everything
    await bootstrapSystemProject(prisma);
    const firstCallCount = vi.mocked(prisma.prompt.create).mock.calls.length;

    // Second call — prompts already exist in mock, should not create again
    await bootstrapSystemProject(prisma);

    // create should not have been called additional times
    expect(vi.mocked(prisma.prompt.create).mock.calls.length).toBe(firstCallCount);
  });

  it('re-creates deleted prompts on subsequent startup', async () => {
    // First run creates everything
    await bootstrapSystemProject(prisma);

    // Simulate deletion: clear the map so findFirst returns null
    vi.mocked(prisma.prompt.findFirst).mockResolvedValue(null);
    vi.mocked(prisma.prompt.create).mockClear();

    // Second run should recreate
    await bootstrapSystemProject(prisma);

    const expectedNames = getSystemPromptNames();
    expect(vi.mocked(prisma.prompt.create).mock.calls.length).toBe(expectedNames.length);
  });

  it('system project has gated=false', async () => {
    await bootstrapSystemProject(prisma);

    const upsertCall = vi.mocked(prisma.project.upsert).mock.calls[0]![0];
    expect((upsertCall as { create: { gated: boolean } }).create.gated).toBe(false);
  });
});

describe('getSystemPromptNames', () => {
  it('returns all system prompt names', () => {
    const names = getSystemPromptNames();
    expect(names).toContain('gate-instructions');
    expect(names).toContain('gate-encouragement');
    expect(names).toContain('gate-intercept-preamble');
    expect(names).toContain('session-greeting');
  });
});
