import type { ViteUserConfig } from 'vitest/config'
import type { TestFsStructure } from '../../test-utils'
import { platform } from 'node:os'
import { resolve } from 'node:path'
import { describe, expect, test } from 'vitest'
import { runVitestCli, useFS } from '../../test-utils'
import { extractToMatchScreenshotPaths } from '../fixtures/expect-dom/utils'
import utilsContent from '../fixtures/expect-dom/utils?raw'

const testFilename = 'basic.test.ts'
const testName = 'screenshot-snapshot'
const bgColor = '#fff'

const testContent = /* ts */`
import { page, server } from 'vitest/browser'
import { describe, test } from 'vitest'
import { render } from './utils'

const dataTestId = 'inline-test'

test('${testName}', async ({ expect }) => {
  render('<div data-testid="' + dataTestId + '" style="background-color: ${bgColor};">Inline Test</div>')

  await expect(page.getByTestId(dataTestId)).toMatchScreenshot()
})
`

const browser = 'chromium'

export async function runInlineTests(
  structure: TestFsStructure,
  config: ViteUserConfig['test'] = {},
) {
  const root = resolve(process.cwd(), `vitest-test-${crypto.randomUUID()}`)

  const fs = useFS(root, {
    ...structure,
    'vitest.config.ts': `
    import { playwright } from '@vitest/browser-playwright'
    export default {
      test: {
        browser: {
          enabled: true,
          screenshotFailures: false,
          provider: playwright(),
          headless: true,
          instances: [{ browser: ${JSON.stringify(browser)} }],
        },
        reporters: ['verbose'],
        ...${JSON.stringify(config)},
      },
    }`,
  })

  const vitest = await runVitestCli({
    nodeOptions: {
      env: {
        CI: 'false',
        GITHUB_ACTIONS: undefined,
        NO_COLOR: 'true',
      },
    },
  }, '--root', root, '--watch')

  return {
    fs,
    root,
    ...vitest,
  }
}

describe('--watch', () => {
  test(
    'fails when creating a snapshot for the first time and does NOT update it afterwards',
    async () => {
      const { fs, stderr, vitest } = await runInlineTests(
        {
          [testFilename]: testContent,
          'utils.ts': utilsContent,
        },
      )

      const [referencePath] = extractToMatchScreenshotPaths(stderr, testName)

      expect(stderr).toContain(`No existing reference screenshot found; a new one was created. Review it before running tests again.\n\nReference screenshot:\n  ${referencePath}`)

      const { atime: _1, atimeMs: _2, ...referenceStat } = fs.statFile(referencePath)

      fs.editFile(testFilename, content => `${content}\n`)

      vitest.resetOutput()
      await vitest.waitForStdout('Test Files  1 passed')

      expect(vitest.stdout).toContain('✓ |chromium| basic.test.ts > screenshot-snapshot')

      const { atime: _3, atimeMs: _4, ...newReferenceStat } = fs.statFile(referencePath)

      expect(referenceStat).toEqual(newReferenceStat)
    },
  )

  test(
    'with --update creates snapshots and updates them on change',
    async () => {
      const { fs, stderr, vitest } = await runInlineTests(
        {
          [testFilename]: testContent,
          'utils.ts': utilsContent,
        },
        {
          update: true,
        },
      )

      expect(stderr).toMatchInlineSnapshot(`""`)

      const referencePath = `__screenshots__/${testFilename}/${testName}-1-${browser}-${platform()}.png`
      const referenceStat = fs.statFile(referencePath)

      fs.editFile(testFilename, content => `${content}\n`)

      vitest.resetOutput()
      await vitest.waitForStdout('Test Files  1 passed')

      expect(vitest.stdout).toContain('✓ |chromium| basic.test.ts > screenshot-snapshot')

      const {
        atime,
        atimeMs,
        ctime,
        ctimeMs,
        mtime,
        mtimeMs,
        ...diffs
      } = fs.statFile(referencePath)

      expect(referenceStat).toEqual(expect.objectContaining(diffs))

      expect(atime.getTime()).toBeGreaterThan(referenceStat.atime.getTime())
      expect(ctime.getTime()).toBeGreaterThan(referenceStat.ctime.getTime())
      expect(mtime.getTime()).toBeGreaterThan(referenceStat.mtime.getTime())

      expect(atimeMs).toBeGreaterThan(referenceStat.atimeMs)
      expect(ctimeMs).toBeGreaterThan(referenceStat.ctimeMs)
      expect(mtimeMs).toBeGreaterThan(referenceStat.mtimeMs)
    },
  )

  test(
    'creates a reference and fails when changing the DOM content',
    async () => {
      const { fs, stderr, vitest } = await runInlineTests(
        {
          [testFilename]: testContent,
          'utils.ts': utilsContent,
        },
      )

      expect(stderr).toContain(`No existing reference screenshot found; a new one was created. Review it before running tests again.\n\nReference screenshot:`)

      fs.editFile(testFilename, content => content.replace(bgColor, '#0ff'))

      vitest.resetOutput()
      await vitest.waitForStdout('Test Files  1 failed')

      expect(vitest.stdout).toContain('× |chromium| basic.test.ts > screenshot-snapshot')
      expect(vitest.stdout).toContain('Screenshot does not match the stored reference.')
      expect(vitest.stdout).toMatch(/\d+ pixels \(ratio 0.\d{2}\) differ\./)
    },
  )
})
