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

const { createWorkbenchSession, postWorkbenchMessage } = vi.hoisted(() => ({
  createWorkbenchSession: vi.fn(),
  postWorkbenchMessage: vi.fn(),
}));

vi.mock('@/lib/workbench/session-store', () => ({
  createWorkbenchSession,
  postWorkbenchMessage,
}));

import { openWorkbenchForExistingCourse } from '@/lib/workbench/existing-course';
import { startConversationWithFirstMessage } from '@/lib/workbench/first-message-session';

describe('openWorkbenchForExistingCourse', () => {
  beforeEach(() => {
    createWorkbenchSession.mockReset();
  });

  it('mints an idle session named by what the caller asked, not by the course', async () => {
    createWorkbenchSession.mockResolvedValue({
      id: 'session-1',
      stageId: 'stage-1',
      status: 'succeeded',
    });

    await expect(
      openWorkbenchForExistingCourse({ stageId: 'stage-1', prompt: '把第三页换个例子' }),
    ).resolves.toMatchObject({ id: 'session-1' });

    expect(createWorkbenchSession).toHaveBeenCalledWith({
      prompt: '把第三页换个例子',
      stageId: 'stage-1',
      // Load-bearing: it is what keeps the row idle and skips run admission, so
      // acquiring a conversation is never billed as a generation.
      existingCourse: true,
    });
  });
});

describe('startConversationWithFirstMessage', () => {
  beforeEach(() => {
    createWorkbenchSession.mockReset();
    postWorkbenchMessage.mockReset();
  });

  it('mints the session, then delivers the message through the ordinary path', async () => {
    const create = vi.fn(async () => ({
      id: 'session-1',
      stageId: 'stage-1',
      status: 'succeeded' as const,
      prompt: '把第三页换个例子',
    }));
    const post = vi.fn(async () => ({ elementRefsAccepted: true, courseRefsAccepted: true }));
    const material = { materialId: 'mat-1', name: 'a.pdf', bytes: 1 };
    const elementRef = {
      kind: 'slide-element' as const,
      stageId: 'stage-1',
      sceneId: 'scene-1',
      elementId: 'title-1',
      elementType: 'text',
      label: '文本',
    };
    const courseRef = { kind: 'course' as const, stageId: 'stage-1', title: '光的折射' };

    const result = await startConversationWithFirstMessage(
      {
        stageId: 'stage-1',
        text: '把第三页换个例子',
        materials: [material],
        elementRefs: [elementRef],
        courseRefs: [courseRef],
      },
      { create, post },
    );

    expect(create).toHaveBeenCalledWith({ stageId: 'stage-1', prompt: '把第三页换个例子' });
    // The refs and the materials go on the MESSAGE: the creation endpoint carries
    // neither, which is why the first message is an ordinary message.
    expect(post).toHaveBeenCalledWith(
      'session-1',
      '把第三页换个例子',
      [material],
      [elementRef],
      [courseRef],
    );
    expect(result).toEqual({
      sessionId: 'session-1',
      elementRefsAccepted: true,
      courseRefsAccepted: true,
    });
  });

  it('names the conversation after the first message, so nothing is titled by its course', async () => {
    const create = vi.fn(async () => ({
      id: 'session-1',
      stageId: 'stage-1',
      status: 'succeeded' as const,
      prompt: '',
    }));
    const post = vi.fn(async () => ({ elementRefsAccepted: false, courseRefsAccepted: false }));

    await startConversationWithFirstMessage(
      { stageId: 'stage-1', text: '帮我把结尾改得更短' },
      { create, post },
    );

    expect(create).toHaveBeenCalledWith({ stageId: 'stage-1', prompt: '帮我把结尾改得更短' });
    expect(post).toHaveBeenCalledWith('session-1', '帮我把结尾改得更短', [], [], []);
  });

  it('does not post when the session could not be created', async () => {
    const create = vi.fn(async () => {
      throw new Error('nope');
    });
    const post = vi.fn();

    await expect(
      startConversationWithFirstMessage({ stageId: 'stage-1', text: 'hi' }, { create, post }),
    ).rejects.toThrow('nope');
    expect(post).not.toHaveBeenCalled();
  });
});