/**
 * Forge Daemon
 *
 * Persistent build/test sandbox that watches for file changes,
 * runs incremental tests, and feeds results back into the AI context.
 *
 * The Forge is the verification engine that makes SANDCODE Flow's
 * autonomous loop work: every code change triggers automatic testing.
 */

import { execFile } from 'child_process'
import { promisify } from 'util'
import { getCwd } from '../../utils/cwd.js'
import { logForDebugging } from '../../utils/debug.js'
import type { TestResults } from '../FlowEngine/types.js'
import type { ForgeConfig, ForgeRunResult, ForgeStatus } from './types.js'
import { detectProjectConfig, detectedToForgeConfig, DEFAULT_FORGE_CONFIG } from './config.js'

const execFileAsync = promisify(execFile)

/** Active Forge status */
let forgeStatus: ForgeStatus = {
  running: false,
  runsCompleted: 0,
  runsPassed: 0,
  runsFailed: 0,
}

/** Current forge config */
let forgeConfig: ForgeConfig = DEFAULT_FORGE_CONFIG

/**
 * Initialize the Forge daemon.
 *
 * Auto-detects project configuration and prepares the sandbox.
 */
export async function initForge(customConfig?: Partial<ForgeConfig>): Promise<ForgeConfig> {
  // Auto-detect project config
  const detected = await detectProjectConfig()
  const autoConfig = detectedToForgeConfig(detected)

  // Merge: auto-detected < custom < defaults
  forgeConfig = {
    ...DEFAULT_FORGE_CONFIG,
    ...autoConfig,
    ...customConfig,
  }

  forgeStatus = {
    running: true,
    runsCompleted: 0,
    runsPassed: 0,
    runsFailed: 0,
    startedAt: Date.now(),
  }

  logForDebugging(`Forge initialized: ${detected.language}/${detected.framework ?? 'plain'}, test: ${forgeConfig.testCommand}`)

  return forgeConfig
}

/**
 * Run tests via the Forge.
 *
 * This is the primary verification method used by the Flow loop.
 * Returns structured TestResults that the loop can act on.
 */
export async function runForgeTests(
  triggerFiles?: string[],
): Promise<TestResults> {
  const startTime = Date.now()

  try {
    const { stdout, stderr } = await execFileAsync(
      forgeConfig.testCommand,
      [],
      {
        cwd: getCwd(),
        timeout: 120_000, // 2 minute timeout
        shell: true,
      },
    )

    const output = stdout + stderr
    const result = parseTestOutput(output, true, Date.now() - startTime, triggerFiles ?? [])

    // Update status
    forgeStatus.runsCompleted++
    forgeStatus.runsPassed++
    forgeStatus.lastResult = result

    logForDebugging(`Forge test run PASSED (${result.duration}ms, ${result.total} tests)`)

    return {
      passed: true,
      total: result.total,
      passedCount: result.passedCount,
      failedCount: 0,
      errors: [],
      duration: result.duration,
      timestamp: Date.now(),
    }
  } catch (error: unknown) {
    const errorOutput = error instanceof Error ? error.message : String(error)
    const result = parseTestOutput(errorOutput, false, Date.now() - startTime, triggerFiles ?? [])

    // Update status
    forgeStatus.runsCompleted++
    forgeStatus.runsFailed++
    forgeStatus.lastResult = result

    logForDebugging(`Forge test run FAILED (${result.duration}ms, ${result.failedCount} failures)`)

    return {
      passed: false,
      total: result.total,
      passedCount: result.passedCount,
      failedCount: result.failedCount,
      errors: result.errors,
      duration: result.duration,
      timestamp: Date.now(),
    }
  }
}

/**
 * Run linter via the Forge.
 */
export async function runForgeLint(
  triggerFiles?: string[],
): Promise<ForgeRunResult> {
  if (!forgeConfig.lintCommand) {
    return {
      type: 'lint',
      passed: true,
      output: 'No lint command configured',
      duration: 0,
      timestamp: Date.now(),
      triggerFiles: triggerFiles ?? [],
      errors: [],
    }
  }

  return runForgeCommand(forgeConfig.lintCommand, 'lint', triggerFiles)
}

/**
 * Run type checker via the Forge.
 */
export async function runForgeTypeCheck(
  triggerFiles?: string[],
): Promise<ForgeRunResult> {
  if (!forgeConfig.typeCheckCommand) {
    return {
      type: 'typecheck',
      passed: true,
      output: 'No type-check command configured',
      duration: 0,
      timestamp: Date.now(),
      triggerFiles: triggerFiles ?? [],
      errors: [],
    }
  }

  return runForgeCommand(forgeConfig.typeCheckCommand, 'typecheck', triggerFiles)
}

/**
 * Get the current Forge status.
 */
export function getForgeStatus(): ForgeStatus {
  return { ...forgeStatus }
}

/**
 * Stop the Forge daemon.
 */
export function stopForge(): void {
  forgeStatus.running = false
  logForDebugging('Forge daemon stopped')
}

/**
 * Get the current Forge config.
 */
export function getForgeConfig(): ForgeConfig {
  return { ...forgeConfig }
}

// --- Internal helpers ---

/**
 * Run a generic Forge command and return structured results.
 */
async function runForgeCommand(
  command: string,
  type: 'test' | 'lint' | 'typecheck',
  triggerFiles?: string[],
): Promise<ForgeRunResult> {
  const startTime = Date.now()

  try {
    const { stdout, stderr } = await execFileAsync(command, [], {
      cwd: getCwd(),
      timeout: 60_000,
      shell: true,
    })

    return {
      type,
      passed: true,
      output: stdout + stderr,
      duration: Date.now() - startTime,
      timestamp: Date.now(),
      triggerFiles: triggerFiles ?? [],
      errors: [],
    }
  } catch (error: unknown) {
    const output = error instanceof Error ? error.message : String(error)

    return {
      type,
      passed: false,
      output,
      duration: Date.now() - startTime,
      timestamp: Date.now(),
      triggerFiles: triggerFiles ?? [],
      errors: extractErrors(output),
    }
  }
}

/**
 * Parse test output to extract structured results.
 *
 * Supports: Jest, Vitest, pytest, go test, cargo test, and generic test runners.
 */
function parseTestOutput(
  output: string,
  passed: boolean,
  duration: number,
  triggerFiles: string[],
): ForgeRunResult {
  let total = 0
  let passedCount = 0
  let failedCount = 0
  const errors = extractErrors(output)

  // Try to parse Jest/Vitest output
  const jestMatch = output.match(/Tests:\s+(\d+) passed(?:,\s+(\d+) failed)?(?:,\s+(\d+) total)?/)
  if (jestMatch) {
    passedCount = parseInt(jestMatch[1] ?? '0', 10)
    failedCount = parseInt(jestMatch[2] ?? '0', 10)
    total = jestMatch[3] ? parseInt(jestMatch[3], 10) : passedCount + failedCount
  }

  // Try to parse pytest output
  const pytestMatch = output.match(/(\d+) passed(?:,\s+(\d+) failed)?/)
  if (pytestMatch && total === 0) {
    passedCount = parseInt(pytestMatch[1] ?? '0', 10)
    failedCount = parseInt(pytestMatch[2] ?? '0', 10)
    total = passedCount + failedCount
  }

  // Try to parse go test output
  const goMatch = output.match(/(?:FAIL|ok)\s+\S+\s+[\d.]+s/)
  if (goMatch && total === 0) {
    total = 1
    if (passed) {
      passedCount = 1
    } else {
      failedCount = 1
    }
  }

  // Fallback: if we couldn't parse, estimate from pass/fail
  if (total === 0) {
    total = 1
    if (passed) {
      passedCount = 1
      failedCount = 0
    } else {
      passedCount = 0
      failedCount = 1
    }
  }

  return {
    type: 'test',
    passed,
    output: output.slice(0, 5000), // Truncate very long output
    duration,
    timestamp: Date.now(),
    triggerFiles,
    errors,
  }
}

/**
 * Extract error lines from test output.
 */
function extractErrors(output: string): string[] {
  const errors: string[] = []
  const lines = output.split('\n')

  for (const line of lines) {
    const trimmed = line.trim()
    if (
      trimmed.startsWith('FAIL') ||
      trimmed.startsWith('Error:') ||
      trimmed.startsWith('error') ||
      trimmed.startsWith('Expected') ||
      trimmed.startsWith('Expected:') ||
      trimmed.includes('AssertionError') ||
      trimmed.includes('TypeError') ||
      trimmed.includes('SyntaxError') ||
      trimmed.includes('FAIL ')
    ) {
      errors.push(trimmed)
    }
  }

  // Limit to 10 most relevant errors
  return errors.slice(0, 10)
}
