import ansis from "ansis"

import type { CommandResult } from "./run.ts"
import type { DiffLine } from "./match.ts"
import { diff, matchOutput } from "./match.ts"

export type TestResult = {
  path: string
  passed: boolean
  commandCount: number
  failures: FailedCommand[]
  error?: string
}

type FailedCommand = {
  result: CommandResult
  diffLines: DiffLine[]
  exitCodeMismatch: boolean
}

export function evaluateFile(
  path: string,
  results: CommandResult[],
  error?: string,
): TestResult {
  if (error) {
    return { path, passed: false, commandCount: results.length, failures: [], error }
  }

  const failures: FailedCommand[] = []

  for (const result of results) {
    const { command, actual, exitCode } = result
    const outputMatches = matchOutput(command.expected, actual)

    let exitCodeMismatch = false
    if (command.exitCode === null) {
      // Expect exit code 0
      exitCodeMismatch = exitCode !== 0
    } else if (command.exitCode === "*") {
      // Expect any non-zero
      exitCodeMismatch = exitCode === 0
    } else {
      // Expect specific code
      exitCodeMismatch = exitCode !== command.exitCode
    }

    if (!outputMatches || exitCodeMismatch) {
      failures.push({
        result,
        diffLines: outputMatches ? [] : diff(command.expected, actual),
        exitCodeMismatch,
      })
    }
  }

  return { path, passed: failures.length === 0, commandCount: results.length, failures }
}

export function formatFailure(test: TestResult): string {
  const lines: string[] = []

  lines.push(ansis.red(`FAIL ${test.path}`))

  if (test.error) {
    lines.push(`  ${ansis.red(test.error)}`)
    return lines.join("\n")
  }

  for (const failure of test.failures) {
    lines.push("")
    lines.push(`  ${ansis.dim("$")} ${failure.result.command.command}`)

    if (failure.diffLines.length > 0) {
      const expectedLines: string[] = []
      const actualLines: string[] = []
      for (const dl of failure.diffLines) {
        const text = dl.kind === "context" ? ansis.dim(dl.text) : dl.text
        if (dl.kind === "expected" || dl.kind === "equal" || dl.kind === "context") {
          const prefix = dl.kind === "expected" ? ansis.green("  > ") : "    "
          expectedLines.push(`${prefix}${text}`)
        }
        if (dl.kind === "actual" || dl.kind === "equal" || dl.kind === "context") {
          const prefix = dl.kind === "actual" ? ansis.red("  > ") : "    "
          actualLines.push(`${prefix}${text}`)
        }
      }
      lines.push(ansis.green("  expected:"), ...expectedLines)
      lines.push(ansis.red("  actual:"), ...actualLines)
    }

    if (failure.exitCodeMismatch) {
      const expected = failure.result.command.exitCode ?? 0
      const actual = failure.result.exitCode
      lines.push(
        ansis.green(`  expected exit code: ${expected === "*" ? "non-zero" : expected}`),
      )
      lines.push(ansis.red(`  actual exit code: ${actual}`))
    }
  }

  return lines.join("\n")
}

export function formatSummary(
  results: TestResult[],
  elapsed: number,
  singleFile?: string,
): string {
  const totalCommands = results.reduce((n, r) => n + r.commandCount, 0)
  const failedCommands = results.reduce((n, r) => n + r.failures.length, 0)
  const passedCommands = totalCommands - failedCommands

  const parts: string[] = []
  if (passedCommands > 0) parts.push(ansis.green(`${passedCommands} passed`))
  if (failedCommands > 0) parts.push(ansis.red(`${failedCommands} failed`))

  const time = elapsed < 1000
    ? `${Math.round(elapsed)}ms`
    : `${(elapsed / 1000).toFixed(1)}s`

  const label = singleFile ? ` in ${singleFile}` : ""
  return `${parts.join(", ")}${label} ${ansis.dim(`[${time}]`)}`
}
