/**
 * cli:test-report — execute.ts
 * Runs test suites and produces a structured report.
 */
import { execSync } from 'node:child_process'
import { writeFileSync, mkdirSync } from 'node:fs'
import { resolve, join } from 'node:path'
import type { TestReportInput, TestReportResult, LayerResult, TestFailure } from './types.js'

const BACKEND_CATEGORIES = ['Seed', 'Domain', 'Business', 'Integration']

export function execute(input: TestReportInput): { report: TestReportResult; reportPath: string } {
  const projectPath = resolve(input.projectPath)
  const start = Date.now()
  const byLayer: Record<string, LayerResult> = {}
  const failures: TestFailure[] = []

  // Run backend tests by category
  if (input.includeBackend) {
    for (const category of BACKEND_CATEGORIES) {
      const result = runDotnetTests(projectPath, category)
      byLayer[category.toLowerCase()] = result.layerResult
      failures.push(...result.failures)
    }
  }

  // Run frontend tests
  if (input.includeFrontend) {
    const result = runFrontendTests(projectPath, input.module)
    byLayer['frontend'] = result.layerResult
    failures.push(...result.failures)
  }

  // Aggregate summary
  const summary = {
    total: Object.values(byLayer).reduce((s, l) => s + l.total, 0),
    passed: Object.values(byLayer).reduce((s, l) => s + l.passed, 0),
    failed: Object.values(byLayer).reduce((s, l) => s + l.failed, 0),
    skipped: Object.values(byLayer).reduce((s, l) => s + l.skipped, 0),
  }

  const report: TestReportResult = {
    module: input.module,
    timestamp: new Date().toISOString(),
    duration: Date.now() - start,
    summary,
    byLayer,
    failures,
    coverage: { line: 0, branch: 0 }, // TODO: integrate with coverage tools
  }

  // Write report
  const reportsDir = join(projectPath, '.ralph', 'reports')
  mkdirSync(reportsDir, { recursive: true })
  const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
  const reportPath = join(reportsDir, `TEST-${input.module}-${ts}.json`)
  writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf-8')

  return { report, reportPath }
}

function runDotnetTests(projectPath: string, category: string): { layerResult: LayerResult; failures: TestFailure[] } {
  try {
    const output = execSync(
      `dotnet test --no-build --filter Category=${category} --verbosity normal --logger "trx;LogFileName=${category}.trx"`,
      { cwd: projectPath, encoding: 'utf-8', timeout: 300_000, stdio: ['pipe', 'pipe', 'pipe'] },
    )

    // Parse test counts from output
    const passed = parseInt(output.match(/Passed:\s+(\d+)/)?.[1] ?? '0')
    const failed = parseInt(output.match(/Failed:\s+(\d+)/)?.[1] ?? '0')
    const skipped = parseInt(output.match(/Skipped:\s+(\d+)/)?.[1] ?? '0')
    const total = passed + failed + skipped

    return {
      layerResult: { total, passed, failed, skipped },
      failures: [],
    }
  } catch (err: any) {
    const output = (err.stdout ?? '') + (err.stderr ?? '')
    const passed = parseInt(output.match(/Passed:\s+(\d+)/)?.[1] ?? '0')
    const failed = parseInt(output.match(/Failed:\s+(\d+)/)?.[1] ?? '0')
    const skipped = parseInt(output.match(/Skipped:\s+(\d+)/)?.[1] ?? '0')
    const total = passed + failed + skipped

    // Extract failure details
    const failureMatches = output.matchAll(/Failed\s+(\S+)\s+\[.*?\]\s*\n\s*(.+)/g)
    const failures: TestFailure[] = []
    for (const match of failureMatches) {
      failures.push({
        test: match[1],
        layer: category.toLowerCase(),
        error: match[2]?.trim() ?? 'Unknown error',
        file: '',
      })
    }

    return {
      layerResult: { total: Math.max(total, 1), passed, failed: Math.max(failed, 1), skipped },
      failures,
    }
  }
}

function runFrontendTests(projectPath: string, module: string): { layerResult: LayerResult; failures: TestFailure[] } {
  try {
    const output = execSync(
      `npm test -- --reporter=json --filter ${module}`,
      { cwd: projectPath, encoding: 'utf-8', timeout: 300_000, stdio: ['pipe', 'pipe', 'pipe'] },
    )

    // Attempt JSON parse of vitest output
    try {
      const json = JSON.parse(output)
      const passed = json.numPassedTests ?? 0
      const failed = json.numFailedTests ?? 0
      const total = json.numTotalTests ?? passed + failed

      return {
        layerResult: { total, passed, failed, skipped: 0 },
        failures: (json.testResults ?? [])
          .filter((t: any) => t.status === 'failed')
          .map((t: any) => ({
            test: t.name ?? 'unknown',
            layer: 'frontend',
            error: t.message ?? 'Test failed',
            file: t.file ?? '',
          })),
      }
    } catch {
      // Fallback: assume pass if exit 0
      return { layerResult: { total: 1, passed: 1, failed: 0, skipped: 0 }, failures: [] }
    }
  } catch (err: any) {
    return {
      layerResult: { total: 1, passed: 0, failed: 1, skipped: 0 },
      failures: [{ test: 'frontend-suite', layer: 'frontend', error: (err.stderr ?? 'Test suite failed').slice(0, 500), file: '' }],
    }
  }
}
