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

const root = process.cwd()
const dataforseoRoot = join(root, 'data', 'sources', 'dataforseo')
const rawRoot = join(dataforseoRoot, 'raw')
const amazonRoot = join(root, 'data', 'sources', 'amazon')
const outRoot = join(root, 'content_population_exports')
const asinMapPath = join(dataforseoRoot, 'asin-map.csv')

mkdirSync(rawRoot, { recursive: true })
mkdirSync(amazonRoot, { recursive: true })
mkdirSync(outRoot, { recursive: true })

function readJson(path: string, fallback: any = null) {
  if (!existsSync(path)) return fallback
  return JSON.parse(readFileSync(path, 'utf8').replace(/^\uFEFF/, ''))
}

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 parseCsv(text: string) {
  const rows: string[][] = []
  let row: string[] = []
  let cell = ''
  let quoted = false
  for (let index = 0; index < text.length; index += 1) {
    const char = text[index]
    const next = text[index + 1]
    if (quoted) {
      if (char === '"' && next === '"') { cell += '"'; index += 1 }
      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 || row.length) { row.push(cell); rows.push(row) }
  const headers = rows.shift()?.map(header => header.trim()) || []
  return rows.filter(values => values.some(value => value.trim())).map(values => Object.fromEntries(headers.map((header, index) => [header, (values[index] || '').trim()])))
}

function ratingValue(value: any) {
  if (value === undefined || value === null || value === '') return null
  if (typeof value === 'object') return ratingValue(value.value)
  const number = Number(value)
  return Number.isFinite(number) ? number : value
}

function tagSku(task: any) {
  const tag = task?.data?.tag || task?.tag || ''
  const match = String(tag).match(/jv:([^:]+):/i)
  return match?.[1] || ''
}

function normaliseTaskResponse(raw: any, file: string, asinRows: any[]) {
  const outputs: any[] = []
  const issues: string[] = []
  const tasks = Array.isArray(raw?.tasks) ? raw.tasks : []
  if (!tasks.length) issues.push('missing tasks array')

  for (const task of tasks) {
    const results = Array.isArray(task?.result) ? task.result : []
    if (!results.length) {
      issues.push(`${file}: task ${task?.id || 'unknown'} has no result array`)
      continue
    }
    for (const result of results) {
      const asin = result?.asin || task?.data?.asin || ''
      const mapped = asinRows.find(row => row.ASIN && row.ASIN.toUpperCase() === String(asin).toUpperCase()) || asinRows.find(row => row.SKU === tagSku(task)) || {}
      const sku = mapped.SKU || tagSku(task) || asin || basename(file, '.json')
      const items = Array.isArray(result?.items) ? result.items : []
      if (!items.length) issues.push(`${file}: ${sku} has no review items`)
      const reviews = items.map((item: any) => ({
        review_title: item?.title || '',
        review_body: item?.review_text || item?.review_body || '',
        rating: ratingValue(item?.rating),
        date: item?.publication_date || item?.date || '',
        helpful_count: item?.helpful_votes ?? item?.helpful_count ?? 0,
        url: item?.url || '',
        verified: item?.verified ?? null,
        reviewer_name: item?.user_profile?.name || '',
        reviewer_url: item?.user_profile?.url || ''
      }))
      const missingText = reviews.filter((review: any) => !review.review_body || String(review.review_body).trim().length < 5).length
      const missingRating = reviews.filter((review: any) => review.rating === null || review.rating === '').length
      if (missingText) issues.push(`${file}: ${sku} has ${missingText} review item(s) without text`)
      if (missingRating) issues.push(`${file}: ${sku} has ${missingRating} review item(s) without rating`)
      outputs.push({
        source: 'amazon',
        provider: 'dataforseo',
        brand: mapped.Product ? 'Just Vitamins' : '',
        sku,
        asin,
        productUrl: mapped.ProductUrl || result?.check_url || result?.image?.url || '',
        capturedAt: result?.datetime || new Date().toISOString(),
        taskId: task?.id || '',
        cost: task?.cost ?? raw?.cost ?? null,
        productTitle: result?.title || mapped.Product || '',
        amazonDomain: result?.se_domain || mapped.AmazonDomain || '',
        averageRating: ratingValue(result?.rating),
        reviewsCount: result?.reviews_count ?? null,
        reviews
      })
    }
  }
  return { outputs, issues }
}

const generatedAt = new Date().toISOString()
const asinRows = existsSync(asinMapPath) ? parseCsv(readFileSync(asinMapPath, 'utf8')) : []
const rawFiles = readdirSync(rawRoot, { withFileTypes: true })
  .filter(entry => entry.isFile() && entry.name.endsWith('.json') && !entry.name.startsWith('_'))
  .map(entry => join(rawRoot, entry.name))

const example = {
  note: 'Paste DataForSEO /v3/merchant/amazon/reviews/task_get/advanced/{id} JSON responses into this raw/ folder. Files starting with _ are ignored.',
  expectedEndpoint: 'GET https://api.dataforseo.com/v3/merchant/amazon/reviews/task_get/advanced/{id}',
  expectedFields: ['tasks[].id', 'tasks[].data.asin', 'tasks[].result[].items[].review_text', 'tasks[].result[].items[].rating.value', 'tasks[].result[].items[].publication_date', 'tasks[].result[].items[].url']
}
writeFileSync(join(rawRoot, '_example-amazon-reviews-task_get-advanced.json'), JSON.stringify(example, null, 2) + '\n', 'utf8')

const rows: any[] = []
const allIssues: string[] = []
let convertedFiles = 0
let convertedReviews = 0
for (const file of rawFiles) {
  try {
    const raw = readJson(file)
    const { outputs, issues } = normaliseTaskResponse(raw, file, asinRows)
    allIssues.push(...issues)
    for (const output of outputs) {
      const safeSku = String(output.sku || output.asin).replace(/[^a-z0-9_-]/gi, '-').toUpperCase()
      const outPath = join(amazonRoot, `dataforseo-${safeSku}.json`)
      writeFileSync(outPath, JSON.stringify(output, null, 2) + '\n', 'utf8')
      convertedFiles += 1
      convertedReviews += output.reviews.length
      rows.push({ RawFile: file, SKU: output.sku, ASIN: output.asin, Reviews: output.reviews.length, OutputFile: outPath, Status: issues.length ? 'converted_with_warnings' : 'converted' })
    }
    if (!outputs.length) rows.push({ RawFile: file, SKU: '', ASIN: '', Reviews: 0, OutputFile: '', Status: 'no_outputs' })
  } catch (error: any) {
    allIssues.push(`${file}: ${error.message}`)
    rows.push({ RawFile: file, SKU: '', ASIN: '', Reviews: 0, OutputFile: '', Status: 'invalid_json' })
  }
}

if (!rawFiles.length) {
  rows.push({ RawFile: '', SKU: '', ASIN: '', Reviews: 0, OutputFile: '', Status: 'waiting_for_raw_dataforseo_results' })
}

const manifest = {
  generatedAt,
  ready: rawFiles.length > 0 && convertedFiles > 0 && allIssues.length === 0,
  rawInputDir: 'data/sources/dataforseo/raw',
  amazonOutputDir: 'data/sources/amazon',
  docs: ['https://docs.dataforseo.com/v3/merchant/amazon/reviews/task_get/advanced/'],
  counts: {
    rawFiles: rawFiles.length,
    convertedFiles,
    convertedReviews,
    issues: allIssues.length,
    asinMapRows: asinRows.length
  },
  issues: allIssues,
  outputs: rows
}

writeFileSync(join(outRoot, 'dataforseo_ingest_validation.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
writeFileSync(join(outRoot, 'dataforseo_ingest_validation.csv'), csv(rows, ['RawFile', 'SKU', 'ASIN', 'Reviews', 'OutputFile', 'Status']), 'utf8')
writeFileSync(join(outRoot, 'dataforseo_ingest_validation.md'), [
  '# DataForSEO Amazon reviews ingest validation',
  '',
  `Generated: ${generatedAt}`,
  '',
  `Ready: ${manifest.ready ? 'YES' : 'NO'}`,
  '',
  '## Counts',
  `- Raw files: ${manifest.counts.rawFiles}`,
  `- Converted Amazon source files: ${manifest.counts.convertedFiles}`,
  `- Converted reviews: ${manifest.counts.convertedReviews}`,
  `- Issues: ${manifest.counts.issues}`,
  '',
  '## How to use after the paid pilot',
  '1. Collect DataForSEO advanced task results from `/v3/merchant/amazon/reviews/task_get/advanced/{id}`.',
  '2. Save each raw response as JSON in `data/sources/dataforseo/raw/`.',
  '3. Run `bun run dataforseo:ingest`.',
  '4. Run `bun run validate:sources` to prove the normalized Amazon drops are usable by the existing source gate.',
  '',
  '## Issues',
  ...(allIssues.length ? allIssues.map(issue => `- ${issue}`) : ['- None']),
  ''
].join('\n'), 'utf8')

console.log(`DataForSEO ingest ready: ${manifest.ready}`)
console.log(`Raw files: ${manifest.counts.rawFiles}`)
console.log(`Converted files: ${manifest.counts.convertedFiles}`)
console.log(`Converted reviews: ${manifest.counts.convertedReviews}`)
console.log(`Issues: ${manifest.counts.issues}`)
if (rawFiles.length > 0 && !manifest.ready) process.exitCode = 1
