import fs, { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { expect, test } from 'vitest'
import { editFile } from '../../test-utils'
import { instances, runBrowserTests } from './utils'

const dir = join(import.meta.dirname, '../fixtures/aria-snapshot')

function extractInlineSnaphsots(code: string) {
  const matches = Array.from(
    code.matchAll(/\.toMatchAriaInlineSnapshot\(\s*`[\s\S]*?`\s*\)/g),
  )
  const snapshots = matches.map((match) => {
    const end = match.index! + match[0].length
    const start = code.lastIndexOf('expect', match.index)
    if (start === -1) {
      throw new Error(`Failed to extract inline snapshot: no expect found for match ${match[0]}`)
    }
    return code.slice(start, end)
  })
  return `\n${snapshots.join('\n\n')}\n`
}

test.for(instances.map(i => i.browser))('aria snapshot %s', async (browser) => {
  const testFile = join(dir, 'basic.test.ts')
  const snapshotFile = join(dir, '__snapshots__/basic.test.ts.snap')

  // clean slate — remove file snapshots and clear inline snapshots
  fs.rmSync(join(dir, '__snapshots__'), { recursive: true, force: true })
  editFile(testFile, s =>
    s.replace(/toMatchAriaInlineSnapshot\(`[^`]*`\)/g, 'toMatchAriaInlineSnapshot()'))

  // run with update: new — creates snapshots from scratch
  let result = await runBrowserTests({
    root: './fixtures/aria-snapshot',
    project: [browser],
    update: 'new',
  })
  expect(result.stderr).toMatchInlineSnapshot(`""`)
  expect(result.errorTree()).toMatchInlineSnapshot(`
    {
      "basic.test.ts": {
        "expect.element aria once": "passed",
        "expect.element aria retry": "passed",
        "poll aria once": "passed",
        "toMatchAriaInlineSnapshot simple": "passed",
        "toMatchAriaSnapshot simple": "passed",
      },
    }
  `)
  expect(readFileSync(snapshotFile, 'utf-8')).toMatchInlineSnapshot(`
    "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

    exports[\`toMatchAriaSnapshot simple 1\`] = \`
    - main:
      - heading "Dashboard" [level=1]
      - navigation "Actions":
        - button "Save"
        - button "Cancel"
    \`;
    "
  `)
  expect(extractInlineSnaphsots(readFileSync(testFile, 'utf-8'))).toMatchInlineSnapshot(`
    "
    expect(document.body).toMatchAriaInlineSnapshot(\`
        - paragraph: Original
        - button "1234": Pattern
      \`)

    expect.poll(async () => {
        document.body.innerHTML = \`<p>poll once</p>\`
        return document.body
      }).toMatchAriaInlineSnapshot(\`- paragraph: poll once\`)

    expect.element(page.getByTestId('body')).toMatchAriaInlineSnapshot(\`
        - heading "Hello" [level=1]
        - paragraph: World
      \`)

    expect.element(page.getByTestId('body'), { interval: 20 })
        .toMatchAriaInlineSnapshot(\`
          - heading "Hello" [level=1]
          - paragraph: World
        \`)
    "
  `)

  // run with update: none — all should pass
  const result2 = await runBrowserTests({
    root: './fixtures/aria-snapshot',
    project: [browser],
    update: 'none',
  })
  expect(result2.stderr).toEqual(result.stderr)
  expect(result2.errorTree()).toEqual(result.errorTree())

  // edit snapshots to add regex patterns, run with none — should still pass
  editFile(snapshotFile, s => s.replace(`navigation "Actions"`, 'navigation /A\\\\w+/'))
  editFile(testFile, s => s.replace(`- button "1234"`, '- button /\\\\d+/'))

  const result3 = await runBrowserTests({
    root: './fixtures/aria-snapshot',
    project: [browser],
    update: 'none',
  })
  expect(result3.stderr).toEqual(result.stderr)
  expect(result3.errorTree()).toEqual(result.errorTree())

  // edit test HTML to break a literal match, run with none — should fail
  editFile(testFile, s => s.replace(`aria-label="Actions"`, `aria-label="EDITED"`))
  editFile(testFile, s => s.replace('<p>Original</p>', '<p>Changed</p>'))

  result = await runBrowserTests({
    root: './fixtures/aria-snapshot',
    project: [browser],
    update: 'none',
  })
  expect(result.errorTree({ stackTrace: true, diff: true })).toMatchInlineSnapshot(`
    {
      "basic.test.ts": {
        "expect.element aria once": "passed",
        "expect.element aria retry": "passed",
        "poll aria once": "passed",
        "toMatchAriaInlineSnapshot simple": [
          "Snapshot \`toMatchAriaInlineSnapshot simple 1\` mismatched
    - Expected
    + Received

    - - paragraph: Original
    + - paragraph: Changed
      - button /\\d+/: Pattern
        at basic.test.ts:22:25",
        ],
        "toMatchAriaSnapshot simple": [
          "Snapshot \`toMatchAriaSnapshot simple 1\` mismatched
    - Expected
    + Received

      - main:
        - heading "Dashboard" [level=1]
    -   - navigation /A\\w+/:
    +   - navigation "EDITED":
          - button "Save"
          - button "Cancel"
        at basic.test.ts:14:25",
        ],
      },
    }
  `)

  // run with update: all — should pass, preserve regex, update mismatched literal
  result = await runBrowserTests({
    root: './fixtures/aria-snapshot',
    project: [browser],
    update: 'all',
  })
  expect(result.stderr).toMatchInlineSnapshot(`""`)
  expect(result.errorTree()).toMatchInlineSnapshot(`
    {
      "basic.test.ts": {
        "expect.element aria once": "passed",
        "expect.element aria retry": "passed",
        "poll aria once": "passed",
        "toMatchAriaInlineSnapshot simple": "passed",
        "toMatchAriaSnapshot simple": "passed",
      },
    }
  `)
  expect(readFileSync(snapshotFile, 'utf-8')).toMatchInlineSnapshot(`
    "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

    exports[\`toMatchAriaSnapshot simple 1\`] = \`
    - main:
      - heading "Dashboard" [level=1]
      - navigation "EDITED":
        - button "Save"
        - button "Cancel"
    \`;
    "
  `)
  expect(extractInlineSnaphsots(readFileSync(testFile, 'utf-8'))).toMatchInlineSnapshot(`
    "
    expect(document.body).toMatchAriaInlineSnapshot(\`
        - paragraph: Changed
        - button /\\\\d+/: Pattern
      \`)

    expect.poll(async () => {
        document.body.innerHTML = \`<p>poll once</p>\`
        return document.body
      }).toMatchAriaInlineSnapshot(\`- paragraph: poll once\`)

    expect.element(page.getByTestId('body')).toMatchAriaInlineSnapshot(\`
        - heading "Hello" [level=1]
        - paragraph: World
      \`)

    expect.element(page.getByTestId('body'), { interval: 20 })
        .toMatchAriaInlineSnapshot(\`
          - heading "Hello" [level=1]
          - paragraph: World
        \`)
    "
  `)
})
