import path from 'node:path'
import { PassThrough, Readable } from 'node:stream'
import { fileURLToPath } from 'node:url'
import { describe, expect, test } from 'vitest'
import { runCommandLine, streamStdin } from '.'
import { normalizeWindowsSeparators } from '../../utils/test-helpers'

let css = normalizeWindowsSeparators(
  path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'fixtures/input.css'),
)

describe('runCommandLine', { timeout: 30_000 }, () => {
  test('canonicalizes, collapses, and sorts candidate groups from positional arguments', async () => {
    let result = await runCommandLine({
      argv: ['--css', css, 'py-3 p-1 px-3'],
      stdinIsTTY: true,
      stdoutIsTTY: false,
    })

    expect(result).toEqual({
      exitCode: 0,
      stdout: 'p-3',
      stderr: '',
    })
  })

  test('falls back to the default tailwind import when --css is omitted', async () => {
    let result = await runCommandLine({
      argv: ['py-3 p-1 px-3'],
      cwd: path.dirname(css),
      stdinIsTTY: true,
      stdoutIsTTY: false,
    })

    expect(result).toEqual({
      exitCode: 0,
      stdout: 'p-3',
      stderr: '',
    })
  })

  test('canonicalizes, collapses, and sorts multiple groups from stdin lines', async () => {
    let result = await runCommandLine({
      argv: ['--css', css],
      stdin: '[display:_flex_] py-3 p-1 px-3\nmt-2 mr-2 mb-2 ml-2 focus:hover:p-3 hover:p-1 py-3\n',
      stdinIsTTY: false,
      stdoutIsTTY: false,
    })

    expect(result).toEqual({
      exitCode: 0,
      stdout: 'flex p-3\nm-2 py-3 hover:p-1 focus:hover:p-3',
      stderr: '',
    })
  })

  test('collapses equivalent candidates', async () => {
    let result = await runCommandLine({
      argv: ['--css', css, 'mt-2 mr-2 mb-2 ml-2'],
      stdinIsTTY: true,
      stdoutIsTTY: false,
    })

    expect(result).toEqual({
      exitCode: 0,
      stdout: 'm-2',
      stderr: '',
    })
  })

  test('renders json output for processed candidate groups', async () => {
    let result = await runCommandLine({
      argv: ['--css', css, '--format', 'json', 'py-3 p-1 px-3'],
      stdinIsTTY: true,
      stdoutIsTTY: false,
    })

    expect(result.exitCode).toBe(0)
    expect(JSON.parse(result.stdout)).toEqual([
      {
        input: 'py-3 p-1 px-3',
        output: 'p-3',
        changed: true,
      },
    ])
    expect(result.stderr).toBe('')
  })

  test('splits candidate lists with segment-aware spacing', async () => {
    let result = await runCommandLine({
      argv: ['--css', css, '--format', 'json', "content-['hello world'] p-1"],
      stdinIsTTY: true,
      stdoutIsTTY: false,
    })

    expect(result.exitCode).toBe(0)
    expect(JSON.parse(result.stdout)).toEqual([
      {
        input: "content-['hello world'] p-1",
        output: "p-1 content-['hello_world']",
        changed: true,
      },
    ])
    expect(result.stderr).toBe('')
  })

  test('shows a usage error when no candidate groups are provided', async () => {
    let result = await runCommandLine({
      argv: ['--css', css],
      stdinIsTTY: true,
      stdoutIsTTY: false,
    })

    expect(result.exitCode).toBe(1)
    expect(result.stderr).toBe('No candidate groups provided')
    expect(result.stdout).toContain('Usage:')
  })

  test('streams canonicalized output line by line', async () => {
    let input = Readable.from('py-3 p-1 px-3\nmt-2 mr-2 mb-2 ml-2\n')
    let { stream: output, collect: collectOutput } = createOutput()

    await streamStdin({ css, cwd: path.dirname(css), format: 'text', input, output })

    expect(collectOutput()).toBe('p-3\nm-2\n')
  })

  test('streams empty lines as empty lines', async () => {
    let input = Readable.from('py-3 p-1 px-3\n\nmt-2 mr-2 mb-2 ml-2\n')
    let { stream: output, collect: collectOutput } = createOutput()

    await streamStdin({ css, cwd: path.dirname(css), format: 'text', input, output })

    expect(collectOutput()).toBe('p-3\n\nm-2\n')
  })

  test('streams json output when requested', async () => {
    let input = Readable.from('py-3 p-1 px-3\nmt-2 mr-2 mb-2 ml-2\n')
    let { stream: output, collect: collectOutput } = createOutput()

    await streamStdin({ css, cwd: path.dirname(css), format: 'json', input, output })

    expect(JSON.parse(collectOutput())).toEqual([
      {
        input: 'py-3 p-1 px-3',
        output: 'p-3',
        changed: true,
      },
      {
        input: 'mt-2 mr-2 mb-2 ml-2',
        output: 'm-2',
        changed: true,
      },
    ])
  })

  test('streams jsonl output when requested', async () => {
    let input = Readable.from('py-3 p-1 px-3\nmt-2 mr-2 mb-2 ml-2\n')
    let { stream: output, collect: collectOutput } = createOutput()

    await streamStdin({ css, cwd: path.dirname(css), format: 'jsonl', input, output })

    expect(collectOutput()).toBe(
      '{"input":"py-3 p-1 px-3","output":"p-3","changed":true}\n' +
        '{"input":"mt-2 mr-2 mb-2 ml-2","output":"m-2","changed":true}\n',
    )
  })
})

function createOutput() {
  let stream = new PassThrough()
  let chunks: Buffer[] = []
  stream.on('data', (chunk) => chunks.push(chunk))
  return { stream, collect: () => Buffer.concat(chunks).toString() }
}