import { test, expect, Page } from './zoo-test'
import { getUtils, createProject } from './test-utils'
import { join } from 'path'
import fs from 'fs'

test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
  test('basic lego happy case', async ({ page, homePage }) => {
    const u = await getUtils(page)

    await test.step('Set up', async () => {
      await page.setBodyDimensions({ width: 1000, height: 500 })
      await homePage.goToModelingScene()
      await u.waitForPageLoad()
    })

    await sendPromptFromCommandBar(page, 'a 2x4 lego')

    // Find the toast.
    // Look out for the toast message
    const submittingToastMessage = page.getByText(
      `Submitting to Text-to-CAD API...`
    )
    await expect(submittingToastMessage).toBeVisible()

    const generatingToastMessage = page.getByText(
      `Generating parametric model...`
    )
    await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })

    const successToastMessage = page.getByText(`Text-to-CAD successful`)
    await expect(successToastMessage).toBeVisible({ timeout: 15000 })

    // Hit accept.
    const copyToClipboardButton = page.getByRole('button', {
      name: 'Accept',
    })
    await expect(copyToClipboardButton).toBeVisible()

    await copyToClipboardButton.click()

    // Click in the code editor.
    await page.locator('.cm-content').click()

    // Expect the code to be pasted.
    await expect(page.locator('.cm-content')).toContainText(`const`)

    // make sure a model renders.
    // wait for execution done
    await u.openDebugPanel()
    await u.expectCmdLog('[data-message-type="execution-done"]')
    await u.closeDebugPanel()
  })

  test('success model, then ignore success toast, user can create new prompt from command bar', async ({
    page,
    homePage,
  }) => {
    const u = await getUtils(page)

    await page.setBodyDimensions({ width: 1000, height: 500 })

    await homePage.goToModelingScene()
    await u.waitForPageLoad()

    await sendPromptFromCommandBar(page, 'a 2x6 lego')

    // Find the toast.
    // Look out for the toast message
    const submittingToastMessage = page.getByText(
      `Submitting to Text-to-CAD API...`
    )
    await expect(submittingToastMessage).toBeVisible()

    const generatingToastMessage = page.getByText(
      `Generating parametric model...`
    )
    await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })

    const successToastMessage = page.getByText(`Text-to-CAD successful`)
    await expect(successToastMessage).toBeVisible({ timeout: 15000 })

    // Can send a new prompt from the command bar.
    await sendPromptFromCommandBar(page, 'a 2x4 lego')

    // Find the toast.
    // Look out for the toast message
    await expect(submittingToastMessage).toBeVisible()
    await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })

    // Expect 2 success toasts.
    await expect(successToastMessage).toHaveCount(2, {
      timeout: 15000,
    })
    await expect(page.getByText('a 2x4 lego')).toBeVisible()
    await expect(page.getByText('a 2x6 lego')).toBeVisible()
  })

  test('you can reject text-to-cad output and it does nothing', async ({
    page,
    homePage,
  }) => {
    const u = await getUtils(page)

    await page.setBodyDimensions({ width: 1000, height: 500 })

    await homePage.goToModelingScene()
    await u.waitForPageLoad()

    await sendPromptFromCommandBar(page, 'a 2x4 lego')

    // Find the toast.
    // Look out for the toast message
    const submittingToastMessage = page.getByText(
      `Submitting to Text-to-CAD API...`
    )
    await expect(submittingToastMessage).toBeVisible()

    const generatingToastMessage = page.getByText(
      `Generating parametric model...`
    )
    await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })

    const successToastMessage = page.getByText(`Text-to-CAD successful`)
    await expect(successToastMessage).toBeVisible({ timeout: 15000 })

    // Hit copy to clipboard.
    const rejectButton = page.getByRole('button', { name: 'Reject' })
    await expect(rejectButton).toBeVisible()

    await rejectButton.click()

    // The toast should disappear.
    await expect(successToastMessage).not.toBeVisible()

    // Expect no code.
    await expect(page.locator('.cm-content')).toContainText(``)
  })

  test('sending a bad prompt fails, can dismiss', async ({
    page,
    homePage,
  }) => {
    const u = await getUtils(page)

    await page.setBodyDimensions({ width: 1000, height: 500 })

    await homePage.goToModelingScene()
    await u.waitForPageLoad()

    const commandBarButton = page.getByRole('button', { name: 'Commands' })
    await expect(commandBarButton).toBeVisible()
    // Click the command bar button
    await commandBarButton.click()

    // Wait for the command bar to appear
    const cmdSearchBar = page.getByPlaceholder('Search commands')
    await expect(cmdSearchBar).toBeVisible()

    const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
    await expect(textToCadCommand.first()).toBeVisible()
    // Click the Text-to-CAD command
    await textToCadCommand.first().click()

    // Enter the prompt.
    const prompt = page.getByRole('textbox', { name: 'Prompt' })
    await expect(prompt.first()).toBeVisible()

    // Type the prompt.
    const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
    await page.keyboard.type(randomPrompt)
    await page.waitForTimeout(1000)
    await page.keyboard.press('Enter')

    // Find the toast.
    // Look out for the toast message
    const submittingToastMessage = page.getByText(
      `Submitting to Text-to-CAD API...`
    )
    await expect(submittingToastMessage).toBeVisible()

    const generatingToastMessage = page.getByText(
      `Generating parametric model...`
    )
    await expect(generatingToastMessage).toBeVisible()

    const failureToastMessage = page.getByText(
      `The prompt must clearly describe a CAD model`
    )
    await expect(failureToastMessage).toBeVisible()

    await page.waitForTimeout(1000)

    // Make sure the toast did not say it was successful.
    const successToastMessage = page.getByText(`Text-to-CAD successful`)
    await expect(successToastMessage).not.toBeVisible()
    await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()

    // Find the toast dismiss button.
    const dismissButton = page.getByRole('button', { name: 'Dismiss' })
    await expect(dismissButton).toBeVisible()
    await dismissButton.click()

    // The toast should disappear.
    await expect(failureToastMessage).not.toBeVisible()
  })

  test('sending a bad prompt fails, can start over from toast', async ({
    page,
    homePage,
  }) => {
    const u = await getUtils(page)

    await page.setBodyDimensions({ width: 1000, height: 500 })

    await homePage.goToModelingScene()
    await u.waitForPageLoad()

    const commandBarButton = page.getByRole('button', { name: 'Commands' })
    await expect(commandBarButton).toBeVisible()
    // Click the command bar button
    await commandBarButton.click()

    // Wait for the command bar to appear
    const cmdSearchBar = page.getByPlaceholder('Search commands')
    await expect(cmdSearchBar).toBeVisible()

    const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
    await expect(textToCadCommand.first()).toBeVisible()
    // Click the Text-to-CAD command
    await textToCadCommand.first().click()

    // Enter the prompt.
    const prompt = page.getByRole('textbox', { name: 'Prompt' })
    await expect(prompt.first()).toBeVisible()

    const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'

    // Type the prompt.
    await page.keyboard.type(badPrompt)
    await page.waitForTimeout(1000)
    await page.keyboard.press('Enter')

    // Find the toast.
    // Look out for the toast message
    const submittingToastMessage = page.getByText(
      `Submitting to Text-to-CAD API...`
    )
    await expect(submittingToastMessage).toBeVisible()

    const generatingToastMessage = page.getByText(
      `Generating parametric model...`
    )
    await expect(generatingToastMessage).toBeVisible()

    const failureToastMessage = page.getByText(
      `The prompt must clearly describe a CAD model`
    )
    await expect(failureToastMessage).toBeVisible()

    await page.waitForTimeout(1000)

    // Make sure the toast did not say it was successful.
    const successToastMessage = page.getByText(`Text-to-CAD successful`)
    await expect(successToastMessage).not.toBeVisible()
    await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()

    // Click the edit prompt button to try again.
    const editPromptButton = page.getByRole('button', { name: 'Edit prompt' })
    await expect(editPromptButton).toBeVisible()
    await editPromptButton.click()

    // The toast should disappear.
    await expect(failureToastMessage).not.toBeVisible()

    // Make sure the old prompt is still there and can be edited.
    await expect(page.locator('textarea')).toContainText(badPrompt)

    // Select all and start a new prompt.
    await page.keyboard.down('ControlOrMeta')
    await page.keyboard.press('KeyA')
    await page.keyboard.up('ControlOrMeta')
    await page.keyboard.type('a 2x4 lego')

    // Submit the new prompt.
    await page.keyboard.press('Enter')

    // Make sure the new prompt works.
    // Find the toast.
    // Look out for the toast message
    await expect(submittingToastMessage).toBeVisible()

    await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })

    await expect(successToastMessage).toBeVisible({ timeout: 15000 })
  })

  test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
    page,
    homePage,
  }) => {
    const u = await getUtils(page)

    await page.setBodyDimensions({ width: 1000, height: 500 })

    await homePage.goToModelingScene()
    await u.waitForPageLoad()

    const commandBarButton = page.getByRole('button', { name: 'Commands' })
    await expect(commandBarButton).toBeVisible()
    // Click the command bar button
    await commandBarButton.click()

    // Wait for the command bar to appear
    const cmdSearchBar = page.getByPlaceholder('Search commands')
    await expect(cmdSearchBar).toBeVisible()

    const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
    await expect(textToCadCommand.first()).toBeVisible()
    // Click the Text-to-CAD command
    await textToCadCommand.first().click()

    // Enter the prompt.
    const prompt = page.getByRole('textbox', { name: 'Prompt' })
    await expect(prompt.first()).toBeVisible()

    const badPrompt = 'akjsndladflajbhflauweyf15;'

    // Type the prompt.
    await page.keyboard.type(badPrompt)
    await page.waitForTimeout(1000)
    await page.keyboard.press('Enter')

    // Find the toast.
    // Look out for the toast message
    const submittingToastMessage = page.getByText(
      `Submitting to Text-to-CAD API...`
    )
    await expect(submittingToastMessage).toBeVisible()

    const generatingToastMessage = page.getByText(
      `Generating parametric model...`
    )
    await expect(generatingToastMessage).toBeVisible()

    const failureToastMessage = page.getByText(
      `The prompt must clearly describe a CAD model`
    )
    await expect(failureToastMessage).toBeVisible()

    await page.waitForTimeout(1000)

    // Make sure the toast did not say it was successful.
    const successToastMessage = page.getByText(`Text-to-CAD successful`)
    await expect(successToastMessage).not.toBeVisible()
    await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()

    // They should be able to try again from the command bar.
    await sendPromptFromCommandBar(page, 'a 2x4 lego')

    // Find the toast.
    // Look out for the toast message
    await expect(submittingToastMessage).toBeVisible()

    await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })

    await expect(successToastMessage).toBeVisible({ timeout: 15000 })

    // old failure toast should stick around.
    await expect(failureToastMessage).toBeVisible()
    await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
  })

  test('ensure you can shift+enter in the prompt box', async ({
    page,
    homePage,
  }) => {
    const u = await getUtils(page)

    await page.setBodyDimensions({ width: 1000, height: 500 })

    await homePage.goToModelingScene()
    await u.waitForPageLoad()

    const promptWithNewline = `a 2x4\nlego`

    const commandBarButton = page.getByRole('button', { name: 'Commands' })
    await expect(commandBarButton).toBeVisible()
    // Click the command bar button
    await commandBarButton.click()

    // Wait for the command bar to appear
    const cmdSearchBar = page.getByPlaceholder('Search commands')
    await expect(cmdSearchBar).toBeVisible()

    const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
    await expect(textToCadCommand.first()).toBeVisible()
    // Click the Text-to-CAD command
    await textToCadCommand.first().click()

    // Enter the prompt.
    const prompt = page.getByRole('textbox', { name: 'Prompt' })
    await expect(prompt.first()).toBeVisible()

    // Type the prompt.
    await page.keyboard.type('a 2x4')
    await page.waitForTimeout(1000)
    await page.keyboard.down('Shift')
    await page.keyboard.press('Enter')
    await page.keyboard.up('Shift')
    await page.keyboard.type('lego')
    await page.waitForTimeout(1000)
    await page.keyboard.press('Enter')

    // Find the toast.
    // Look out for the toast message
    const submittingToastMessage = page.getByText(
      `Submitting to Text-to-CAD API...`
    )
    await expect(submittingToastMessage).toBeVisible()

    const generatingToastMessage = page.getByText(
      `Generating parametric model...`
    )
    await expect(generatingToastMessage).toBeVisible({ timeout: 10000 })

    const successToastMessage = page.getByText(`Text-to-CAD successful`)
    await expect(successToastMessage).toBeVisible({ timeout: 15000 })

    await expect(page.getByText(promptWithNewline)).toBeVisible()
  })

  test(
    'can do many at once and get many prompts back, and interact with many',
    { tag: ['@skipWin'] },
    async ({ page, homePage }) => {
      // Let this test run longer since we've seen it timeout.
      test.setTimeout(180_000)

      const u = await getUtils(page)

      await page.setBodyDimensions({ width: 1000, height: 500 })

      await homePage.goToModelingScene()
      await u.waitForPageLoad()

      await sendPromptFromCommandBar(page, 'a 2x4 lego')

      await sendPromptFromCommandBar(page, 'a 2x8 lego')

      await sendPromptFromCommandBar(page, 'a 2x10 lego')

      // Find the toast.
      // Look out for the toast message
      const submittingToastMessage = page.getByText(
        `Submitting to Text-to-CAD API...`
      )
      await expect(submittingToastMessage.first()).toBeVisible()

      const generatingToastMessage = page.getByText(
        `Generating parametric model...`
      )
      await expect(generatingToastMessage.first()).toBeVisible({
        timeout: 10_000,
      })

      const successToastMessage = page.getByText(`Text-to-CAD successful`)
      // We should have three success toasts.
      await expect(successToastMessage).toHaveCount(3, { timeout: 25_000 })

      await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
      await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
      await expect(page.getByText(`a 2x10 lego`)).toBeVisible()

      // Ensure if you reject one, the others stay.
      const rejectButton = page.getByRole('button', { name: 'Reject' })
      await expect(rejectButton.first()).toBeVisible()
      // Click the reject button on the first toast.
      await rejectButton.first().click()

      // The first toast should disappear, but not the others.
      await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
      await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
      await expect(page.getByText(`a 2x4 lego`)).toBeVisible()

      // Ensure you can copy the code for one of the models remaining.
      const copyToClipboardButton = page.getByRole('button', {
        name: 'Accept',
      })
      await expect(copyToClipboardButton.first()).toBeVisible()
      // Click the button.
      await copyToClipboardButton.first().click()

      // Expect the code to be pasted.
      await expect(page.locator('.cm-content')).toContainText(`2x8`)

      // Ensure the final toast remains.
      await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
      await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible()
      await expect(page.getByText(`a 2x4 lego`)).toBeVisible()

      // Ensure you can copy the code for the final model.
      await expect(copyToClipboardButton).toBeVisible()
      // Click the button.
      await copyToClipboardButton.click()

      // Expect the code to be pasted.
      await expect(page.locator('.cm-content')).toContainText(`2x4`)
    }
  )

  test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
    page,
    homePage,
  }) => {
    const u = await getUtils(page)

    await page.setBodyDimensions({ width: 1000, height: 500 })

    await homePage.goToModelingScene()
    await u.waitForPageLoad()

    await sendPromptFromCommandBar(page, 'a 2x4 lego')

    await sendPromptFromCommandBar(page, 'alkjsdnlajshdbfjlhsbdf a;askjdnf')

    // Find the toast.
    // Look out for the toast message
    const submittingToastMessage = page.getByText(
      `Submitting to Text-to-CAD API...`
    )
    await expect(submittingToastMessage.first()).toBeVisible()

    const generatingToastMessage = page.getByText(
      `Generating parametric model...`
    )
    await expect(generatingToastMessage.first()).toBeVisible({ timeout: 10000 })

    const successToastMessage = page.getByText(`Text-to-CAD successful`)
    // We should have three success toasts.
    await expect(successToastMessage).toHaveCount(1, { timeout: 15000 })

    await expect(page.getByText('Copied')).not.toBeVisible()

    const failureToastMessage = page.getByText(
      `The prompt must clearly describe a CAD model`
    )
    await expect(failureToastMessage).toBeVisible()

    // Make sure the toast did not say it was successful.
    await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()

    await expect(page.getByText(`a 2x4 lego`)).toBeVisible()

    // Ensure if you dismiss the error the others stay.
    const dismissButton = page.getByRole('button', { name: 'Dismiss' })
    await expect(dismissButton).toBeVisible()
    // Click the dismiss button on the first toast.
    await dismissButton.first().click()

    // Make sure the failure toast disappears.
    await expect(failureToastMessage).not.toBeVisible()
    await expect(page.getByText(`Text-to-CAD failed`)).not.toBeVisible()

    // The first toast should disappear, but not the others.
    await expect(page.getByText(`a 2x4 lego`)).toBeVisible()

    // Ensure you can copy the code for one of the models remaining.
    const copyToClipboardButton = page.getByRole('button', {
      name: 'Accept',
    })
    await expect(copyToClipboardButton.first()).toBeVisible()
    // Click the button.
    await copyToClipboardButton.first().click()

    // Expect the code to be pasted.
    await expect(page.locator('.cm-content')).toContainText(`2x4`)
  })
})

async function sendPromptFromCommandBar(page: Page, promptStr: string) {
  await page.waitForTimeout(1000)
  await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
    const commandBarButton = page.getByRole('button', { name: 'Commands' })
    await expect(commandBarButton).toBeVisible()
    // Click the command bar button
    await commandBarButton.hover()
    await commandBarButton.click()
    await page.waitForTimeout(1000)

    // Wait for the command bar to appear
    const cmdSearchBar = page.getByPlaceholder('Search commands')
    await expect(cmdSearchBar).toBeVisible()

    const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API')
    await expect(textToCadCommand.first()).toBeVisible()
    // Click the Text-to-CAD command
    await textToCadCommand.first().scrollIntoViewIfNeeded()
    await textToCadCommand.first().click()
    await page.waitForTimeout(1000)

    // Enter the prompt.
    const prompt = page.getByRole('textbox', { name: 'Prompt' })
    await expect(prompt.first()).toBeVisible()

    // Type the prompt.
    await page.keyboard.type(promptStr)
    await page.waitForTimeout(200)
    await page.keyboard.press('Enter')
  })
}

test(
  'Text-to-CAD functionality',
  { tag: '@electron' },
  async ({ context, page }, testInfo) => {
    const projectName = 'project-000'
    const prompt = 'lego 2x4'
    const textToCadFileName = 'lego-2x4.kcl'

    const { dir } = await context.folderSetupFn(async () => {})

    const fileExists = () =>
      fs.existsSync(join(dir, projectName, textToCadFileName))

    const { openFilePanel, openKclCodePanel, waitForPageLoad } = await getUtils(
      page,
      test
    )

    await page.setBodyDimensions({ width: 1200, height: 500 })

    // Locators
    const projectMenuButton = page
      .getByTestId('project-sidebar-toggle')
      .filter({ hasText: projectName })
    const textToCadFileButton = page.getByRole('listitem').filter({
      has: page.getByRole('button', { name: textToCadFileName }),
    })
    const textToCadComment = page.getByText(
      `// Generated by Text-to-CAD: ${prompt}`
    )

    // Create and navigate to the project
    await createProject({ name: 'project-000', page })

    // Wait for Start Sketch otherwise you will not have access Text-to-CAD command
    await waitForPageLoad()
    await openFilePanel()
    await openKclCodePanel()

    await test.step(`Test file creation`, async () => {
      await sendPromptFromCommandBar(page, prompt)
      // File is considered created if it shows up in the Project Files pane
      await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
      expect(fileExists()).toBeTruthy()
    })

    await test.step(`Test file navigation`, async () => {
      await expect(projectMenuButton).toContainText('main.kcl')
      await textToCadFileButton.click()
      // File can be navigated and loaded assuming a specific KCL comment is loaded into the KCL code pane
      await expect(textToCadComment).toBeVisible({ timeout: 20_000 })
      await expect(projectMenuButton).toContainText(textToCadFileName)
    })

    await test.step(`Test file deletion on rejection`, async () => {
      const rejectButton = page.getByRole('button', { name: 'Reject' })
      // A file is created and can be navigated to while this prompt is still opened
      // Click the "Reject" button within the prompt and it will delete the file.
      await rejectButton.click()

      const submittingToastMessage = page.getByText(
        `Successfully deleted file "lego-2x4.kcl"`
      )
      await expect(submittingToastMessage).toBeVisible()
      expect(fileExists()).toBeFalsy()
      // Confirm we've navigated back to the main.kcl file after deletion
      await expect(projectMenuButton).toContainText('main.kcl')
    })
  }
)
