#!/usr/bin/env bun
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs'
import { join } from 'path'

const root = process.cwd()
const outRoot = join(root, 'content_population_exports')
mkdirSync(outRoot, { recursive: true })

function readText(path: string) {
  return existsSync(path) ? readFileSync(path, 'utf8').replace(/^\uFEFF/, '') : ''
}
function readJson(path: string, fallback: any = null) {
  if (!existsSync(path)) return fallback
  return JSON.parse(readText(path))
}
function parseCsv(text: string) {
  const rows: string[][] = []
  let row: string[] = []
  let cell = ''
  let quoted = false
  for (let i = 0; i < text.length; i++) {
    const char = text[i]
    const next = text[i + 1]
    if (quoted) {
      if (char === '"' && next === '"') { cell += '"'; i++ }
      else if (char === '"') quoted = false
      else cell += char
    } else {
      if (char === '"') quoted = true
      else if (char === ',') { row.push(cell); cell = '' }
      else if (char === '\n') { row.push(cell); rows.push(row); row = []; cell = '' }
      else if (char !== '\r') cell += char
    }
  }
  if (cell.length || row.length) { row.push(cell); rows.push(row) }
  const [headers = [], ...body] = rows.filter(r => r.some(c => c.trim() !== ''))
  return body.map(values => Object.fromEntries(headers.map((h, i) => [h, values[i] ?? ''])))
}
function csv(rows: any[], headers: string[]) {
  const esc = (value: any) => {
    if (value === undefined || value === null) value = ''
    if (Array.isArray(value)) value = value.join(' | ')
    value = String(value)
    return /[",\n\r]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value
  }
  return [headers.join(','), ...rows.map(row => headers.map(header => esc(row[header])).join(','))].join('\n') + '\n'
}
function yes(value: any) {
  return String(value || '').trim().toLowerCase() === 'yes' || value === true
}
function nonempty(value: any) {
  return String(value || '').trim().length > 0
}
function isoish(value: any) {
  const text = String(value || '').trim()
  if (!text) return false
  const time = Date.parse(text)
  return Number.isFinite(time)
}
function pushRow(rows: any[], area: string, file: string, check: string, status: 'pass' | 'blocked' | 'missing', evidence: string, nextAction: string, guardrail: string) {
  rows.push({ Area: area, File: file, Check: check, Status: status, Evidence: evidence, NextAction: nextAction, Guardrail: guardrail })
}

const generatedAt = new Date().toISOString()
const rows: any[] = []

const clinicalPath = 'data/content/compliance/clinical-blank-decisions.csv'
const clinicalRows = parseCsv(readText(join(root, clinicalPath)))
const clinicalApproved = clinicalRows.filter(row => row.decision === 'approved_blank' && nonempty(row.approvedBy) && isoish(row.approvedAt) && nonempty(row.rationale) && yes(row.constraintsAcknowledged)).length
const clinicalPending = clinicalRows.filter(row => row.decision !== 'approved_blank').length
pushRow(rows, 'Compliance clinical blanks', clinicalPath, 'All blank clinical claim rows have explicit approved_blank decisions with approver, date, rationale, and constraints acknowledgement.', clinicalRows.length && clinicalPending === 0 ? 'pass' : clinicalRows.length ? 'blocked' : 'missing', `${clinicalApproved}/${clinicalRows.length} valid approved_blank rows; ${clinicalPending || clinicalRows.length} still pending/invalid.`, 'Compliance owner must fill the CSV or provide source-backed claims, then run bun run compliance:blank-decisions && bun run claims:sync-content.', 'Do not invent clinical claims or approve blanks without compliance input.')

const approvalPath = 'data/content/approvals/product-approval-decisions.csv'
const approvalRows = parseCsv(readText(join(root, approvalPath)))
const validApprovalRows = approvalRows.filter(row => row.decision === 'approve_all' && nonempty(row.approvedBy) && isoish(row.approvedAt) && nonempty(row.reviewEvidence) && yes(row.contentReviewed) && yes(row.complianceReviewed) && yes(row.imagesReviewed) && yes(row.exportReviewed) && yes(row.noFakeEvidenceAcknowledged)).length
const approvalPending = approvalRows.length - validApprovalRows
pushRow(rows, 'Product approval decisions', approvalPath, 'All products have human approve_all rows with all evidence/acknowledgement fields complete.', approvalRows.length && approvalPending === 0 ? 'pass' : approvalRows.length ? 'blocked' : 'missing', `${validApprovalRows}/${approvalRows.length} valid approve_all rows; ${approvalPending || approvalRows.length} still pending/invalid.`, 'Umar/JV reviewer must complete the CSV, then run bun run approval:decisions && bun run approval:review-packet.', 'Do not self-approve products or treat generated drafts as human approval.')

const providerPath = 'data/sources/provider-responses/dataforseo-reviews-endpoint-response.json'
const provider = readJson(join(root, providerPath), null)
const providerReady = provider && provider.responseReceived === true && (provider.endpointAvailable === true || nonempty(provider.replacementEndpoint)) && provider.providerSaysRetrySafe === true && provider.credentialIncludedInSupportThread === false
pushRow(rows, 'DataForSEO provider response', providerPath, 'Support response proves the Reviews endpoint or replacement endpoint is available and retry-safe without leaking credentials.', providerReady ? 'pass' : provider ? 'blocked' : 'missing', provider ? `responseReceived=${provider.responseReceived}; endpointAvailable=${provider.endpointAvailable}; replacementEndpoint=${provider.replacementEndpoint || 'blank'}; retrySafe=${provider.providerSaysRetrySafe}; credentialIncluded=${provider.credentialIncludedInSupportThread}` : 'Provider response file missing.', 'Wait for DataForSEO support/docs clearance, fill the provider response tracker, then run bun run dataforseo:support-response.', 'Use the real pi-fresh-foundation credentials only in shell env; never paste or store secrets in the response tracker.')

const spendPath = 'data/sources/approvals/dataforseo-stage1-approval.json'
const spend = readJson(join(root, spendPath), null)
const spendReady = spend && spend.approved === true && nonempty(spend.approvedBy) && isoish(spend.approvedAt) && Number(spend.maxSpendApproved) > 0 && Number(spend.maxSpendApproved) <= Number(spend.expectedMaxSpend || spend.maxSpendApproved) && Number(spend.targetCapApproved) === Number(spend.expectedTargetCap) && Number(spend.runLimitApproved) === Number(spend.expectedRunLimit) && Number(spend.depthOrRequestCapApproved) === Number(spend.expectedDepthOrRequestCap) && spend.hardStopAcknowledged === true && spend.noCredentialStorageAcknowledged === true && spend.postRunReviewRequired === true
pushRow(rows, 'DataForSEO spend approval', spendPath, 'Stage 1 approval file has approver, date, capped spend, exact target/run/depth caps, and hard-stop/no-secret/post-review acknowledgements.', spendReady ? 'pass' : spend ? 'blocked' : 'missing', spend ? `approved=${spend.approved}; maxSpendApproved=${spend.maxSpendApproved}; targetCap=${spend.targetCapApproved}/${spend.expectedTargetCap}; runLimit=${spend.runLimitApproved}/${spend.expectedRunLimit}; depth=${spend.depthOrRequestCapApproved}/${spend.expectedDepthOrRequestCap}; hardStop=${spend.hardStopAcknowledged}; noCredentialStorage=${spend.noCredentialStorageAcknowledged}` : 'Approval file missing.', 'Project owner must explicitly fill the approval file after endpoint clearance, then run bun run source:spend-gate.', 'Approval alone must not override provider endpoint/support/docs blockers.')

const importLogDir = 'data/staging/import-logs'
const importLogFull = join(root, importLogDir)
const importLogs = existsSync(importLogFull) ? readdirSync(importLogFull).filter(file => file.endsWith('.json')) : []
pushRow(rows, 'Staging import evidence', importLogDir, 'At least one real Matrixify/staging import log JSON exists outside templates.', importLogs.length > 0 ? 'pass' : existsSync(importLogFull) ? 'blocked' : 'missing', `${importLogs.length} import log file(s) found.`, 'Lewis/Euan must save real import logs under data/staging/import-logs/ and rerun bun run staging:evidence.', 'Queued templates are not import evidence.')

const pdpDir = 'data/staging/pdp-checks'
const pdpFull = join(root, pdpDir)
const pdpChecks = existsSync(pdpFull) ? readdirSync(pdpFull).filter(file => file.endsWith('.json')) : []
pushRow(rows, 'Staging PDP spot checks', pdpDir, 'At least 5 real staging PDP spot-check JSON files exist outside the queue/templates.', pdpChecks.length >= 5 ? 'pass' : existsSync(pdpFull) ? 'blocked' : 'missing', `${pdpChecks.length}/5 PDP check file(s) found.`, 'Save real staging URL spot checks under data/staging/pdp-checks/ and rerun bun run staging:evidence.', 'Queue files under pdp-check-queue are assignments only, not completed checks.')

const cutoverPath = 'data/staging/cutover-approval.json'
const cutover = readJson(join(root, cutoverPath), null)
const cutoverReady = cutover && (cutover.approved === true || cutover.cutoverApproved === true) && nonempty(cutover.approvedBy || cutover.signoffBy) && isoish(cutover.approvedAt || cutover.signoffAt)
pushRow(rows, 'Staging cutover approval', cutoverPath, 'Cutover approval exists with explicit approval/signoff owner and timestamp.', cutoverReady ? 'pass' : cutover ? 'blocked' : 'missing', cutover ? `approved=${cutover.approved ?? cutover.cutoverApproved}; approver=${cutover.approvedBy || cutover.signoffBy || 'blank'}; approvedAt=${cutover.approvedAt || cutover.signoffAt || 'blank'}` : 'Cutover approval file missing.', 'Project owner/Lewis/Euan must sign the cutover approval after successful staging checks.', 'Do not treat handoff packet generation as cutover approval.')

const blockingRows = rows.filter(row => row.Status !== 'pass')
const manifest = {
  generatedAt,
  ready: blockingRows.length === 0,
  purpose: 'Preflight validation for the human-editable reviewer/provider/staging input files before rerunning the heavier handoff gates.',
  counts: {
    checks: rows.length,
    passing: rows.filter(row => row.Status === 'pass').length,
    blocked: rows.filter(row => row.Status === 'blocked').length,
    missing: rows.filter(row => row.Status === 'missing').length
  },
  rows,
  blockingRows,
  files: {
    markdown: 'content_population_exports/reviewer_input_preflight.md',
    json: 'content_population_exports/reviewer_input_preflight.json',
    csv: 'content_population_exports/reviewer_input_preflight.csv'
  }
}

writeFileSync(join(outRoot, 'reviewer_input_preflight.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
writeFileSync(join(outRoot, 'reviewer_input_preflight.csv'), csv(rows, ['Area', 'File', 'Check', 'Status', 'Evidence', 'NextAction', 'Guardrail']), 'utf8')
writeFileSync(join(outRoot, 'reviewer_input_preflight.md'), [
  '# Reviewer input preflight',
  '',
  `Generated: ${generatedAt}`,
  '',
  `Ready: **${manifest.ready ? 'YES' : 'NO'}**`,
  `Checks passing: ${manifest.counts.passing}/${manifest.counts.checks}`,
  `Blocked: ${manifest.counts.blocked}`,
  `Missing: ${manifest.counts.missing}`,
  '',
  'This preflight validates only reviewer-owned input files. It does not approve content, claim provider availability, spend credits, or replace `bun run verify:handoff`.',
  '',
  '## Blocking rows',
  ...(blockingRows.length ? blockingRows.map(row => `- [${String(row.Status).toUpperCase()}] ${row.Area}: ${row.Evidence} Next: ${row.NextAction}`) : ['- none']),
  '',
  '## All checks',
  '| Area | Status | Evidence | File |',
  '|---|---|---|---|',
  ...rows.map(row => `| ${row.Area} | ${row.Status} | ${String(row.Evidence).replace(/\|/g, '/')} | ${row.File} |`),
  '',
  '## Guardrail',
  'Use this before asking reviewers to rerun expensive/heavy gates. If it is blocked, fix the named editable input file first; do not infer decisions from generated packets.',
  ''
].join('\n'), 'utf8')

console.log(`Reviewer input preflight ready: ${manifest.ready}`)
console.log(`Checks passing: ${manifest.counts.passing}/${manifest.counts.checks}`)
console.log(`Blocked: ${manifest.counts.blocked}`)
console.log(`Missing: ${manifest.counts.missing}`)
if (!manifest.ready) process.exitCode = 1
