import { test, expect } from './coverage-fixtures.js'

// Seeds two-message chat into localStorage so we don't need a live model.
async function seedChat(page, history) {
  await page.addInitScript((h) => {
    const chat = {
      id: 'seed1', name: 'Seeded Chat', model: 'test-model',
      history: h, systemPrompt: '', mcpMode: false, mcpServers: [],
      clientMCPServers: [], temperature: null, topP: null, topK: null,
      tokenUsage: { prompt: 0, completion: 0, total: 0 },
      contextSize: null, createdAt: Date.now(), updatedAt: Date.now(),
    }
    localStorage.setItem('localai_chats_data', JSON.stringify({
      chats: [chat], activeChatId: 'seed1', lastSaved: Date.now(),
    }))
  }, history)
}

async function mockModels(page) {
  await page.route('**/api/models/capabilities', (route) => route.fulfill({
    contentType: 'application/json',
    body: JSON.stringify({ data: [{ id: 'test-model', capabilities: ['FLAG_CHAT'] }] }),
  }))
  await page.route('**/api/operations', (route) => route.fulfill({
    contentType: 'application/json', body: JSON.stringify({ operations: [] }),
  }))
}

const TWO_TURNS = [
  { role: 'user', content: 'first question' },
  { role: 'assistant', content: 'first answer' },
  { role: 'user', content: 'second question' },
  { role: 'assistant', content: 'second answer' },
]

test('duplicate creates an independent copy and switches to it', async ({ page }) => {
  await mockModels(page)
  await seedChat(page, TWO_TURNS)
  await page.goto('/app/chat')

  // Open the chats menu (Ctrl/Cmd+K) and duplicate the seeded chat.
  // Wait for the menu trigger to mount so its global keydown listener is armed
  // before we dispatch the shortcut.
  await page.getByTitle('Conversations (Ctrl/Cmd+K)').waitFor()
  await page.keyboard.press('Control+k')
  await page.getByTitle('Duplicate chat').first().click()

  // A new active chat named "Seeded Chat (fork)" with the same 4 messages.
  await expect(page.locator('.chat-header-title')).toHaveText('Seeded Chat (fork)')
  await expect(page.locator('.chat-message-user')).toHaveCount(2)
  await expect(page.locator('.chat-message-assistant')).toHaveCount(2)
})

async function mockCompletion(page, replyText) {
  await page.route('**/v1/chat/completions', (route) => {
    const sse =
      `data: ${JSON.stringify({ choices: [{ delta: { content: replyText } }] })}\n\n` +
      `data: ${JSON.stringify({ choices: [{ delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } })}\n\n` +
      `data: [DONE]\n\n`
    route.fulfill({ status: 200, contentType: 'text/event-stream', body: sse })
  })
}

test('retry regenerates the first answer and drops the later turn', async ({ page }) => {
  await mockModels(page)
  // Capture the outbound request body so we can assert the model receives the
  // truncated history (not the stale downstream turns).
  let sentMessages = null
  await page.route('**/v1/chat/completions', (route) => {
    sentMessages = route.request().postDataJSON()?.messages || []
    const sse =
      `data: ${JSON.stringify({ choices: [{ delta: { content: 'REGENERATED first answer' } }] })}\n\n` +
      `data: ${JSON.stringify({ choices: [{ delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 } })}\n\n` +
      `data: [DONE]\n\n`
    route.fulfill({ status: 200, contentType: 'text/event-stream', body: sse })
  })
  await seedChat(page, TWO_TURNS)
  await page.goto('/app/chat')

  // Hover the FIRST assistant message and click its retry button.
  const firstAssistant = page.locator('.chat-message-assistant').first()
  await firstAssistant.hover()
  await firstAssistant.getByTitle('Regenerate').click()

  // History is truncated to the first user turn, then the new answer streams in;
  // the second Q/A turn is gone.
  await expect(page.locator('.chat-message-assistant')).toContainText(['REGENERATED first answer'])
  await expect(page.locator('.chat-message-user')).toHaveCount(1)
  await expect(page.locator('.chat-message-assistant')).toHaveCount(1)

  // The OUTBOUND payload must also be truncated: the resent user turn is present,
  // but the downstream turn and the stale first answer must be gone.
  const contents = (sentMessages || []).map(m =>
    typeof m.content === 'string' ? m.content : JSON.stringify(m.content)
  )
  expect(contents.join('\n')).toContain('first question')
  expect(contents.join('\n')).not.toContain('second question')
  expect(contents.join('\n')).not.toContain('first answer')
})

test('copy chat puts the whole conversation on the clipboard', async ({ page, context }) => {
  await context.grantPermissions(['clipboard-read', 'clipboard-write'])
  await mockModels(page)
  await seedChat(page, TWO_TURNS)
  await page.goto('/app/chat')

  // Wait for the menu trigger to mount so its global keydown listener is armed
  // before we dispatch the shortcut (same mount-race guard as the duplicate test).
  await page.getByTitle('Conversations (Ctrl/Cmd+K)').waitFor()
  await page.keyboard.press('Control+k')
  await page.getByTitle('Copy chat').first().click()

  const clip = await page.evaluate(() => navigator.clipboard.readText())
  expect(clip).toContain('# Seeded Chat')
  expect(clip).toContain('first answer')
  expect(clip).toContain('second answer')
})

test('branch from the first answer forks history up to that point', async ({ page }) => {
  await mockModels(page)
  await seedChat(page, TWO_TURNS)
  await page.goto('/app/chat')

  const firstAssistant = page.locator('.chat-message-assistant').first()
  await firstAssistant.hover()
  await firstAssistant.getByTitle('Branch from here').click()

  // New active chat "Seeded Chat (fork)" contains only the first Q/A turn.
  await expect(page.locator('.chat-header-title')).toHaveText('Seeded Chat (fork)')
  await expect(page.locator('.chat-message-user')).toHaveCount(1)
  await expect(page.locator('.chat-message-assistant')).toHaveCount(1)
  await expect(page.locator('.chat-message-assistant')).toContainText(['first answer'])
})
