/**
 * cli:readiness-report — execute.ts
 * Appelle les 3 validators (roslyn, eslint, cross-validate) et agregge les resultats
 * Ne fait PAS de validation d'input — c'est le role de validate.ts
 */
import { execSync } from 'node:child_process'
import type { ReadinessInput, ReadinessReport, SourceScore } from './types'

// ─── Constants ───

/** Roslyn: 10 rules, 4 pts each = 40 max */
const ROSLYN_RULES_COUNT = 10
const ROSLYN_PTS_PER_RULE = 4
const ROSLYN_MAX = ROSLYN_RULES_COUNT * ROSLYN_PTS_PER_RULE // 40

/** ESLint: 5 rules, 5 pts each = 25 max */
const ESLINT_RULES_COUNT = 5
const ESLINT_PTS_PER_RULE = 5
const ESLINT_MAX = ESLINT_RULES_COUNT * ESLINT_PTS_PER_RULE // 25

/** Cross-validate: 5 checks, 7 pts each = 35 max */
const CROSS_CHECKS_COUNT = 5
const CROSS_PTS_PER_CHECK = 7
const CROSS_MAX = CROSS_CHECKS_COUNT * CROSS_PTS_PER_CHECK // 35

// ─── Run a validator subprocess and parse JSON output ───

function runValidator(cmd: string, cwd: string): any {
  try {
    const output = execSync(cmd, {
      encoding: 'utf-8',
      timeout: 60000,
      cwd,
      stdio: ['pipe', 'pipe', 'pipe'],
    })
    return JSON.parse(output)
  } catch (e: any) {
    // Validator may exit non-zero (errors found) but still produce JSON on stdout
    if (e.stdout) {
      try { return JSON.parse(e.stdout) } catch { /* fall through */ }
    }
    return null
  }
}

// ─── Roslyn scoring ───

function scoreRoslyn(report: any): SourceScore {
  if (!report || !Array.isArray(report.checks)) {
    return {
      score: 0,
      max: ROSLYN_MAX,
      status: 'skipped',
      errors: 0,
      details: ['roslyn-validator did not produce a valid report'],
    }
  }

  // Collect unique rule codes that have errors
  const errorCodes = new Set<string>()
  const allCodes = new Set<string>()
  const errorDetails: string[] = []

  for (const check of report.checks) {
    if (check.code) allCodes.add(check.code)
    if (check.status === 'error') {
      errorCodes.add(check.code)
      const detail = check.file
        ? `${check.code}: ${check.file}${check.line ? ':' + check.line : ''} -- ${check.message || ''}`
        : `${check.code}: ${check.message || 'error'}`
      errorDetails.push(detail)
    }
  }

  // Score: 4 pts per rule without errors (out of 10 rules)
  const rulesWithErrors = errorCodes.size
  const rulesOk = ROSLYN_RULES_COUNT - rulesWithErrors
  const score = Math.max(0, rulesOk * ROSLYN_PTS_PER_RULE)

  const status = rulesWithErrors === 0 ? 'ok' : rulesWithErrors <= 2 ? 'warning' : 'error'

  return {
    score,
    max: ROSLYN_MAX,
    status,
    errors: rulesWithErrors,
    details: errorDetails,
  }
}

// ─── ESLint scoring ───

function scoreEslint(report: any): SourceScore {
  if (!report || !Array.isArray(report.checks)) {
    return {
      score: 0,
      max: ESLINT_MAX,
      status: 'skipped',
      errors: 0,
      details: ['eslint-validator did not produce a valid report'],
    }
  }

  // Collect unique rule codes that have errors
  const errorCodes = new Set<string>()
  const errorDetails: string[] = []

  for (const check of report.checks) {
    if (check.status === 'error') {
      errorCodes.add(check.code)
      const detail = check.file
        ? `${check.code}: ${check.file}${check.line ? ':' + check.line : ''} -- ${check.message || ''}`
        : `${check.code}: ${check.message || 'error'}`
      errorDetails.push(detail)
    }
  }

  // Score: 5 pts per rule without errors (out of 5 rules)
  const rulesWithErrors = errorCodes.size
  const rulesOk = ESLINT_RULES_COUNT - rulesWithErrors
  const score = Math.max(0, rulesOk * ESLINT_PTS_PER_RULE)

  const status = rulesWithErrors === 0 ? 'ok' : rulesWithErrors <= 1 ? 'warning' : 'error'

  return {
    score,
    max: ESLINT_MAX,
    status,
    errors: rulesWithErrors,
    details: errorDetails,
  }
}

// ─── Cross-validate scoring ───

function scoreCrossValidate(report: any): SourceScore {
  if (!report) {
    return {
      score: 0,
      max: CROSS_MAX,
      status: 'skipped',
      errors: 0,
      details: ['cross-validate-stack did not produce a valid report'],
    }
  }

  // cross-validate may have { checks: [...] } or { blockers: [...], warnings: [...] }
  const errorDetails: string[] = []

  if (Array.isArray(report.checks)) {
    // Same format as roslyn/eslint: checks array with code/status
    const errorCodes = new Set<string>()
    for (const check of report.checks) {
      if (check.status === 'error') {
        errorCodes.add(check.code)
        const detail = check.file
          ? `${check.code}: ${check.file} -- ${check.message || ''}`
          : `${check.code}: ${check.message || 'error'}`
        errorDetails.push(detail)
      }
    }
    const checksWithErrors = errorCodes.size
    const checksOk = CROSS_CHECKS_COUNT - checksWithErrors
    const score = Math.max(0, checksOk * CROSS_PTS_PER_CHECK)
    const status = checksWithErrors === 0 ? 'ok' : checksWithErrors <= 1 ? 'warning' : 'error'
    return { score, max: CROSS_MAX, status, errors: checksWithErrors, details: errorDetails }
  }

  // Fallback: blockers/warnings format (stub format)
  if (Array.isArray(report.blockers)) {
    for (const b of report.blockers) {
      errorDetails.push(String(b))
    }
  }

  const blockerCount = Array.isArray(report.blockers) ? report.blockers.length : 0
  const checksOk = CROSS_CHECKS_COUNT - blockerCount
  const score = Math.max(0, checksOk * CROSS_PTS_PER_CHECK)
  const status = blockerCount === 0 ? 'ok' : blockerCount <= 1 ? 'warning' : 'error'

  return { score, max: CROSS_MAX, status, errors: blockerCount, details: errorDetails }
}

// ─── Determine recommendation ───

function getRecommendation(total: number): 'GO' | 'NO-GO' | 'STOP' {
  if (total >= 90) return 'GO'
  if (total >= 70) return 'NO-GO'
  return 'STOP'
}

// ─── Main execute function ───

export function execute(input: ReadinessInput): ReadinessReport {
  const { specs, src } = input

  // CLI is always invoked from the project root (cwd)
  const projectRoot = process.cwd()

  const roslynCmd = `npx --prefer-offline tsx skills/validation/roslyn/cli/index.ts --src "${src}"`
  const eslintCmd = `npx --prefer-offline tsx skills/validation/eslint/cli/index.ts --src "${src}"`
  const crossCmd = `npx --prefer-offline tsx skills/validation/cross-validate/cli/index.ts --specs "${specs}" --src "${src}"`

  // ─── Run validators ───
  const roslynOutput = runValidator(roslynCmd, projectRoot)
  const eslintOutput = runValidator(eslintCmd, projectRoot)
  const crossOutput = runValidator(crossCmd, projectRoot)

  // ─── Score each source ───
  const roslynScore = scoreRoslyn(roslynOutput)
  const eslintScore = scoreEslint(eslintOutput)
  const crossScore = scoreCrossValidate(crossOutput)

  // ─── Aggregate ───
  const total = roslynScore.score + eslintScore.score + crossScore.score
  const recommendation = getRecommendation(total)

  // ─── Collect blockers ───
  const blockers: string[] = []
  if (roslynScore.details.length > 0) blockers.push(...roslynScore.details)
  if (eslintScore.details.length > 0) blockers.push(...eslintScore.details)
  if (crossScore.details.length > 0) blockers.push(...crossScore.details)

  return {
    timestamp: new Date().toISOString(),
    scores: {
      roslyn: roslynScore,
      eslint: eslintScore,
      'cross-validate': crossScore,
    },
    total,
    max: 100,
    recommendation,
    blockers,
  }
}
