import { test, expect } from './zoo-test'
import { join } from 'path'
import fsp from 'fs/promises'
import { getUtils, executorInputPath, createProject } from './test-utils'
import { bracket } from 'lib/exampleKcl'
import { onboardingPaths } from 'routes/Onboarding/paths'
import {
  TEST_SETTINGS_KEY,
  TEST_SETTINGS_ONBOARDING_START,
  TEST_SETTINGS_ONBOARDING_EXPORT,
  TEST_SETTINGS_ONBOARDING_USER_MENU,
} from './storageStates'
import * as TOML from '@iarna/toml'
import { expectPixelColor } from './fixtures/sceneFixture'

// Because onboarding relies on an app setting we need to set it as incompletel
// for all these tests.

test.describe('Onboarding tests', () => {
  test(
    'Onboarding code is shown in the editor',
    {
      appSettings: {
        app: {
          onboardingStatus: 'incomplete',
        },
      },
      cleanProjectDir: true,
    },
    async ({ page, homePage }) => {
      const u = await getUtils(page)
      await page.setBodyDimensions({ width: 1200, height: 500 })
      await homePage.goToModelingScene()

      // Test that the onboarding pane loaded
      await expect(
        page.getByText('Welcome to Modeling App! This')
      ).toBeVisible()

      // Test that the onboarding pane loaded
      await expect(
        page.getByText('Welcome to Modeling App! This')
      ).toBeVisible()

      // *and* that the code is shown in the editor
      await expect(page.locator('.cm-content')).toContainText(
        '// Shelf Bracket'
      )

      // Make sure the model loaded
      const XYPlanePoint = { x: 774, y: 116 } as const
      const modelColor: [number, number, number] = [45, 45, 45]
      await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
      expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(
        8
      )
    }
  )

  test(
    'Desktop: fresh onboarding executes and loads',
    {
      tag: '@electron',
      appSettings: {
        app: {
          onboardingStatus: 'incomplete',
        },
      },
      cleanProjectDir: true,
    },
    async ({ page }) => {
      const u = await getUtils(page)

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

      await test.step(`Create a project and open to the onboarding`, async () => {
        await createProject({ name: 'project-link', page })
        await test.step(`Ensure the engine connection works by testing the sketch button`, async () => {
          await u.waitForPageLoad()
        })
      })

      await test.step(`Ensure we see the onboarding stuff`, async () => {
        // Test that the onboarding pane loaded
        await expect(
          page.getByText('Welcome to Modeling App! This')
        ).toBeVisible()

        // *and* that the code is shown in the editor
        await expect(page.locator('.cm-content')).toContainText(
          '// Shelf Bracket'
        )

        // TODO: jess make less shit
        // Make sure the model loaded
        //const XYPlanePoint = { x: 986, y: 522 } as const
        //const modelColor: [number, number, number] = [76, 76, 76]
        //await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)

        //await expectPixelColor(page, modelColor, XYPlanePoint, 8)
      })
    }
  )

  test(
    'Code resets after confirmation',
    {
      appSettings: {
        app: {
          onboardingStatus: 'incomplete',
        },
      },
      cleanProjectDir: true,
    },
    async ({ context, page, homePage }) => {
      const initialCode = `sketch001 = startSketchOn('XZ')`

      // Load the page up with some code so we see the confirmation warning
      // when we go to replay onboarding
      await context.addInitScript((code) => {
        localStorage.setItem('persistCode', code)
      }, initialCode)

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

      // Replay the onboarding
      await page.getByRole('link', { name: 'Settings' }).last().click()
      const replayButton = page.getByRole('button', {
        name: 'Replay onboarding',
      })
      await expect(replayButton).toBeVisible()
      await replayButton.click()

      // Ensure we see the warning, and that the code has not yet updated
      await expect(page.getByText('Would you like to create')).toBeVisible()
      await expect(page.locator('.cm-content')).toHaveText(initialCode)

      const nextButton = page.getByTestId('onboarding-next')
      await nextButton.hover()
      await nextButton.click()

      // Ensure we see the introduction and that the code has been reset
      await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
      await expect(page.locator('.cm-content')).toContainText(
        '// Shelf Bracket'
      )

      // There used to be old code here that checked if we stored the reset
      // code into localStorage but that isn't the case on desktop. It gets
      // saved to the file system, which we have other tests for.
    }
  )

  test(
    'Click through each onboarding step and back',
    {
      appSettings: {
        app: {
          onboardingStatus: 'incomplete',
        },
      },
    },
    async ({ context, page, homePage }) => {
      // Override beforeEach test setup
      await context.addInitScript(
        async ({ settingsKey, settings }) => {
          // Give no initial code, so that the onboarding start is shown immediately
          localStorage.setItem('persistCode', '')
          localStorage.setItem(settingsKey, settings)
        },
        {
          settingsKey: TEST_SETTINGS_KEY,
          settings: TOML.stringify({
            settings: TEST_SETTINGS_ONBOARDING_START,
          }),
        }
      )

      await page.setBodyDimensions({ width: 1200, height: 1080 })
      await homePage.goToModelingScene()

      // Test that the onboarding pane loaded
      await expect(
        page.getByText('Welcome to Modeling App! This')
      ).toBeVisible()

      const nextButton = page.getByTestId('onboarding-next')
      const prevButton = page.getByTestId('onboarding-prev')

      while ((await nextButton.innerText()) !== 'Finish') {
        await nextButton.hover()
        await nextButton.click()
      }

      while ((await prevButton.innerText()) !== 'Dismiss') {
        await prevButton.hover()
        await prevButton.click()
      }

      // Dismiss the onboarding
      await prevButton.hover()
      await prevButton.click()

      // Test that the onboarding pane is gone
      await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
      await expect.poll(() => page.url()).not.toContain('/onboarding')
    }
  )

  test(
    'Onboarding redirects and code updating',
    {
      appSettings: {
        app: {
          onboardingStatus: '/export',
        },
      },
      cleanProjectDir: true,
    },
    async ({ context, page, homePage }) => {
      const originalCode = 'sigmaAllow = 15000'

      // Override beforeEach test setup
      await context.addInitScript(
        async ({ settingsKey, settings }) => {
          // Give some initial code, so we can test that it's cleared
          localStorage.setItem('persistCode', originalCode)
          localStorage.setItem(settingsKey, settings)
        },
        {
          settingsKey: TEST_SETTINGS_KEY,
          settings: TOML.stringify({
            settings: TEST_SETTINGS_ONBOARDING_EXPORT,
          }),
        }
      )

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

      // Test that the redirect happened
      await expect.poll(() => page.url()).toContain('/onboarding/export')

      // Test that you come back to this page when you refresh
      await page.reload()
      await expect.poll(() => page.url()).toContain('/onboarding/export')

      // Test that the code changes when you advance to the next step
      await page.getByTestId('onboarding-next').hover()
      await page.getByTestId('onboarding-next').click()

      // Test that the onboarding pane loaded
      const title = page.locator('[data-testid="onboarding-content"]')
      await expect(title).toBeAttached()

      await expect(page.locator('.cm-content')).not.toHaveText(originalCode)

      // Test that the code is not empty when you click on the next step
      await page.locator('[data-testid="onboarding-next"]').hover()
      await page.locator('[data-testid="onboarding-next"]').click()
      await expect(page.locator('.cm-content')).toHaveText(/.+/)
    }
  )

  test(
    'Onboarding code gets reset to demo on Interactive Numbers step',
    {
      appSettings: {
        app: {
          onboardingStatus: '/parametric-modeling',
        },
      },
      cleanProjectDir: true,
    },

    async ({ page, homePage }) => {
      const u = await getUtils(page)
      const badCode = `// This is bad code we shouldn't see`

      await page.setBodyDimensions({ width: 1200, height: 1080 })
      await homePage.goToModelingScene()

      await expect
        .poll(() => page.url())
        .toContain(onboardingPaths.PARAMETRIC_MODELING)

      const bracketNoNewLines = bracket.replace(/\n/g, '')

      // Check the code got reset on load
      await expect(page.locator('#code-pane')).toBeVisible()
      await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
        timeout: 10_000,
      })

      // Mess with the code again
      await u.codeLocator.selectText()
      await u.codeLocator.fill(badCode)
      await expect(u.codeLocator).toHaveText(badCode)

      // Click to the next step
      await page.locator('[data-testid="onboarding-next"]').hover()
      await page.locator('[data-testid="onboarding-next"]').click()
      await page.waitForURL('**' + onboardingPaths.INTERACTIVE_NUMBERS, {
        waitUntil: 'domcontentloaded',
      })

      // Check that the code has been reset
      await expect(u.codeLocator).toHaveText(bracketNoNewLines)
    }
  )

  // (lee) The two avatar tests are weird because even on main, we don't have
  // anything to do with the avatar inside the onboarding test. Due to the
  // low impact of an avatar not showing I'm changing this to fixme.
  test.fixme(
    'Avatar text updates depending on image load success',
    {
      appSettings: {
        app: {
          onboardingStatus: 'incomplete',
        },
      },
      cleanProjectDir: true,
    },
    async ({ context, page, homePage }) => {
      // Override beforeEach test setup
      await context.addInitScript(
        async ({ settingsKey, settings }) => {
          localStorage.setItem(settingsKey, settings)
        },
        {
          settingsKey: TEST_SETTINGS_KEY,
          settings: TOML.stringify({
            settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
          }),
        }
      )

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

      // Test that the text in this step is correct
      const avatarLocator = page
        .getByTestId('user-sidebar-toggle')
        .locator('img')
      const onboardingOverlayLocator = page
        .getByTestId('onboarding-content')
        .locator('div')
        .nth(1)

      // Expect the avatar to be visible and for the text to reference it
      await expect(avatarLocator).toBeVisible()
      await expect(onboardingOverlayLocator).toBeVisible()
      await expect(onboardingOverlayLocator).toContainText('your avatar')

      // This is to force the avatar to 404.
      // For our test image (only triggers locally. on CI, it's Kurt's /
      // gravatar image )
      await page.route('/cat.jpg', async (route) => {
        await route.fulfill({
          status: 404,
          contentType: 'text/plain',
          body: 'Not Found!',
        })
      })

      // 404 the CI avatar image
      await page.route(
        'https://lh3.googleusercontent.com/**',
        async (route) => {
          await route.fulfill({
            status: 404,
            contentType: 'text/plain',
            body: 'Not Found!',
          })
        }
      )

      await page.reload({ waitUntil: 'domcontentloaded' })

      // Now expect the text to be different
      await expect(avatarLocator).not.toBeVisible()
      await expect(onboardingOverlayLocator).toBeVisible()
      await expect(onboardingOverlayLocator).toContainText('the menu button')
    }
  )

  test.fixme(
    "Avatar text doesn't mention avatar when no avatar",
    {
      appSettings: {
        app: {
          onboardingStatus: 'incomplete',
        },
      },
      cleanProjectDir: true,
    },
    async ({ context, page, homePage }) => {
      // Override beforeEach test setup
      await context.addInitScript(
        async ({ settingsKey, settings }) => {
          localStorage.setItem(settingsKey, settings)
          localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE')
        },
        {
          settingsKey: TEST_SETTINGS_KEY,
          settings: TOML.stringify({
            settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
          }),
        }
      )

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

      // Test that the text in this step is correct
      const sidebar = page.getByTestId('user-sidebar-toggle')
      const avatar = sidebar.locator('img')
      const onboardingOverlayLocator = page
        .getByTestId('onboarding-content')
        .locator('div')
        .nth(1)

      // Expect the avatar to be visible and for the text to reference it
      await expect(avatar).not.toBeVisible()
      await expect(onboardingOverlayLocator).toBeVisible()
      await expect(onboardingOverlayLocator).toContainText('the menu button')

      // Test we mention what else is in this menu for https://github.com/KittyCAD/modeling-app/issues/2939
      // which doesn't deserver its own full test spun up
      const userMenuFeatures = [
        'manage your account',
        'report a bug',
        'request a feature',
        'sign out',
      ]
      for (const feature of userMenuFeatures) {
        await expect(onboardingOverlayLocator).toContainText(feature)
      }
    }
  )
})

test.fixme(
  'Restarting onboarding on desktop takes one attempt',
  {
    appSettings: {
      app: {
        onboardingStatus: 'dismissed',
      },
    },
    cleanProjectDir: true,
  },
  async ({ context, page }) => {
    await context.folderSetupFn(async (dir) => {
      const routerTemplateDir = join(dir, 'router-template-slate')
      await fsp.mkdir(routerTemplateDir, { recursive: true })
      await fsp.copyFile(
        executorInputPath('router-template-slate.kcl'),
        join(routerTemplateDir, 'main.kcl')
      )
    })

    // Our constants
    const u = await getUtils(page)
    const projectCard = page.getByText('router-template-slate')
    const helpMenuButton = page.getByRole('button', {
      name: 'Help and resources',
    })
    const restartOnboardingButton = page.getByRole('button', {
      name: 'Reset onboarding',
    })
    const nextButton = page.getByTestId('onboarding-next')

    const tutorialProjectIndicator = page
      .getByTestId('project-sidebar-toggle')
      .filter({ hasText: 'Tutorial Project 00' })
    const tutorialModalText = page.getByText('Welcome to Modeling App!')
    const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
    const userMenuButton = page.getByTestId('user-sidebar-toggle')
    const userMenuSettingsButton = page.getByRole('button', {
      name: 'User settings',
    })
    const settingsHeading = page.getByRole('heading', {
      name: 'Settings',
      exact: true,
    })
    const restartOnboardingSettingsButton = page.getByRole('button', {
      name: 'Replay onboarding',
    })

    await test.step('Navigate into project', async () => {
      await expect(
        page.getByRole('heading', { name: 'Your Projects' })
      ).toBeVisible()
      await expect(projectCard).toBeVisible()
      await projectCard.click()
      await u.waitForPageLoad()
    })

    await test.step('Restart the onboarding from help menu', async () => {
      await helpMenuButton.click()
      await restartOnboardingButton.click()

      await nextButton.hover()
      await nextButton.click()
    })

    await test.step('Confirm that the onboarding has restarted', async () => {
      await expect(tutorialProjectIndicator).toBeVisible()
      await expect(tutorialModalText).toBeVisible()
      // Make sure the model loaded
      const XYPlanePoint = { x: 988, y: 523 } as const
      const modelColor: [number, number, number] = [76, 76, 76]

      await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
      await expectPixelColor(page, modelColor, XYPlanePoint, 8)
      await tutorialDismissButton.click()
      // Make sure model still there.
      await expectPixelColor(page, modelColor, XYPlanePoint, 8)
    })

    await test.step('Clear code and restart onboarding from settings', async () => {
      await u.openKclCodePanel()
      await expect(u.codeLocator).toContainText('// Shelf Bracket')
      await u.codeLocator.selectText()
      await u.codeLocator.fill('')

      await test.step('Navigate to settings', async () => {
        await userMenuButton.click()
        await userMenuSettingsButton.click()
        await expect(settingsHeading).toBeVisible()
        await expect(restartOnboardingSettingsButton).toBeVisible()
      })

      await restartOnboardingSettingsButton.click()
      // Since the code is empty, we should not see the confirmation dialog
      await expect(nextButton).not.toBeVisible()
      await expect(tutorialProjectIndicator).toBeVisible()
      await expect(tutorialModalText).toBeVisible()
    })
  }
)
