import { appendFileSync } from 'node:fs'

import type { FetchOutcome } from './cloudNodes'

let hasReported = false

export function resetCloudNodesReporterForTests(): void {
  hasReported = false
}

export function reportCloudNodesOutcome(outcome: FetchOutcome): void {
  if (hasReported) return
  hasReported = true

  const lines = buildAnnotations(outcome)
  for (const line of lines) {
    process.stdout.write(`${line}\n`)
  }

  const summaryPath = process.env.GITHUB_STEP_SUMMARY
  if (summaryPath) {
    try {
      appendFileSync(summaryPath, buildStepSummary(outcome))
    } catch {
      // Writing the summary is best-effort; do not fail the build if the
      // runner's summary file is unavailable (e.g. local dev).
    }
  }
}

function buildAnnotations(outcome: FetchOutcome): string[] {
  if (outcome.status === 'fresh') {
    if (outcome.droppedCount === 0) return []
    const nodeCount = outcome.droppedCount === 1 ? 'node' : 'nodes'
    const drops = outcome.droppedNodes
      .map((d) => {
        const name = escapeAnnotation(d.name ? `"${d.name}"` : '(unnamed)')
        const reason = escapeAnnotation(d.reason)
        return `  - ${name}: ${reason}`
      })
      .join('%0A')
    return [
      `::warning title=Cloud nodes: dropped ${outcome.droppedCount} invalid ${nodeCount}::Dropped nodes:%0A${drops}%0A%0AAction items:%0A  1. Verify node definitions returned by cloud /api/object_info.%0A  2. If a valid node shape changed, update @comfyorg/object-info-parser/src/schemas/nodeDefSchema.ts and add tests.%0A  3. Dropped nodes are not shown on /cloud/supported-nodes until fixed.`
    ]
  }

  if (outcome.status === 'stale') {
    return [staleAnnotation(outcome.reason)]
  }

  return [
    `::error title=Cloud nodes fetch failed and no snapshot is available::Cannot build cloud nodes page without data.%0A%0AReason: ${escapeAnnotation(outcome.reason)}%0A%0AAction items:%0A  1. Run \`pnpm --filter @comfyorg/website cloud-nodes:refresh-snapshot\` locally with a valid WEBSITE_CLOUD_API_KEY.%0A  2. Commit apps/website/src/data/cloud-nodes.snapshot.json.%0A  3. Push and re-run CI.`
  ]
}

function staleAnnotation(reason: string): string {
  const escaped = escapeAnnotation(reason)
  if (reason.startsWith('missing ')) {
    return `::warning title=Cloud nodes integration::${escaped}. Falling back to committed snapshot.%0A%0AAction items:%0A  1. If you're a contributor without key access, this is expected. The snapshot will be used.%0A  2. If this is CI, check that the \`WEBSITE_CLOUD_API_KEY\` secret exists in the repo and is referenced in .github/workflows/ci-website-build.yaml.`
  }
  if (reason.startsWith('HTTP 401') || reason.startsWith('HTTP 403')) {
    return `::error title=Cloud nodes authentication failed::${escaped}. The WEBSITE_CLOUD_API_KEY is missing, invalid, or revoked. Build continues with the last-known-good snapshot.%0A%0AAction items:%0A  1. Verify the Cloud API key is active and scoped for /api/object_info.%0A  2. Update the \`WEBSITE_CLOUD_API_KEY\` secret in GitHub Actions and Vercel.%0A  3. Re-run this workflow.`
  }
  if (reason.startsWith('envelope')) {
    return `::error title=Cloud nodes schema mismatch::${escaped}. The Cloud API contract likely changed. Build continues with the snapshot, but future updates will fail until schema is fixed.%0A%0AAction items:%0A  1. Check cloud/services/ingest/openapi.yaml for /api/object_info changes.%0A  2. Update apps/website/src/utils/cloudNodes.schema.ts and @comfyorg/object-info-parser schema as needed.`
  }
  return `::warning title=Cloud nodes API unavailable::${escaped}. Using last-known-good snapshot.%0A%0AAction items:%0A  1. Check cloud service health.%0A  2. Re-run this workflow once cloud.comfy.org is healthy.`
}

function escapeAnnotation(value: string): string {
  return value.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A')
}

function buildStepSummary(outcome: FetchOutcome): string {
  const header = '## ☁️ Cloud nodes\n'
  const rows: Array<[string, string]> = []

  if (outcome.status === 'fresh') {
    rows.push(['Status', '✅ Fresh (fetched from Cloud API)'])
    rows.push(['Packs', String(outcome.snapshot.packs.length)])
    rows.push([
      'Nodes',
      String(outcome.snapshot.packs.reduce((n, p) => n + p.nodes.length, 0))
    ])
    rows.push(['Dropped', String(outcome.droppedCount)])
  } else if (outcome.status === 'stale') {
    rows.push(['Status', '⚠️ Stale (using snapshot — Cloud fetch failed)'])
    rows.push(['Packs', String(outcome.snapshot.packs.length)])
    rows.push([
      'Nodes',
      String(outcome.snapshot.packs.reduce((n, p) => n + p.nodes.length, 0))
    ])
    rows.push(['Reason', outcome.reason])
    rows.push(['Snapshot age', describeSnapshotAge(outcome.snapshot.fetchedAt)])
  } else {
    rows.push(['Status', '❌ Failed (no snapshot available)'])
    rows.push(['Reason', outcome.reason])
  }

  const table =
    '| | |\n|---|---|\n' +
    rows.map(([k, v]) => `| **${k}** | ${v} |`).join('\n') +
    '\n'

  return `${header}${table}\n`
}

function describeSnapshotAge(fetchedAt: string): string {
  const fetched = new Date(fetchedAt).getTime()
  if (Number.isNaN(fetched)) return 'unknown'
  const days = Math.floor((Date.now() - fetched) / 86_400_000)
  if (days <= 0) return 'today'
  if (days === 1) return '1 day'
  return `${days} days`
}
