/**
 * Performance & stability check against a synthetic catalog.
 *   pnpm perf            # 50k products
 *   pnpm perf 100000
 * Measures sync duration + memory and Catalog API latency (p50/p95/max under 10 concurrent callers).
 */
import { fork } from 'node:child_process'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import postgres from 'postgres'
import { createDb, runMigrations } from '@znet/catalog-db'
import { createLogger } from '@znet/shared'
import { MagentoApi, MagentoClient } from '@znet/magento'
import { buildApp } from '../../apps/catalog-api/src/app.js'
import { syncInventory, syncProductsFull, syncProductsIncremental, syncReference, type SyncContext } from '../../apps/sync/src/jobs.js'

const TOTAL = Number(process.argv[2] ?? 50_000)
const DB_URL = process.env.PERF_DATABASE_URL ?? 'postgres://localhost/mcp_znet_perf'
const HERE = path.dirname(fileURLToPath(import.meta.url))
const TOKEN = 'perf-token-0123456789-0123456789'

const mb = (b: number) => `${Math.round(b / 1024 / 1024)} MB`
const rows: Array<[string, string]> = []
const report = (k: string, v: string) => {
  rows.push([k, v])
  console.log(`${k.padEnd(44)} ${v}`)
}

// ── setup ───────────────────────────────────────────────────────────────────
{
  const u = new URL(DB_URL)
  const name = u.pathname.slice(1)
  u.pathname = '/postgres'
  const admin = postgres(u.toString(), { max: 1, onnotice: () => {} })
  await admin.unsafe(`DROP DATABASE IF EXISTS "${name}" WITH (FORCE)`)
  await admin.unsafe(`CREATE DATABASE "${name}"`)
  await admin.end()
}
await runMigrations(DB_URL)

const child = fork(path.join(HERE, 'mock-server.ts'), [String(TOTAL)], {
  execArgv: ['--import', 'tsx', '--conditions=znet-source'],
  stdio: ['ignore', 'pipe', 'inherit', 'ipc'],
})
const mock = await new Promise<{ url: string; token: string; products: number; sourceItems: number }>(resolve => {
  child.stdout!.once('data', d => resolve(JSON.parse(String(d))))
})
report('Synthetic catalog', `${mock.products.toLocaleString()} products, ${mock.sourceItems.toLocaleString()} source items`)

const { db, close } = createDb(DB_URL, { max: 10 })
const ctx: SyncContext = {
  db,
  magento: new MagentoApi(new MagentoClient({ baseUrl: mock.url, accessToken: mock.token })),
  log: createLogger('perf', 'warn'),
  storeCodes: [],
  stockId: 1,
}

let peakRss = 0
const sampler = setInterval(() => (peakRss = Math.max(peakRss, process.memoryUsage().rss)), 50)
async function timed<T>(label: string, fn: () => Promise<T>): Promise<T> {
  peakRss = process.memoryUsage().rss
  const t = performance.now()
  const out = await fn()
  report(label, `${((performance.now() - t) / 1000).toFixed(1)} s   (peak RSS ${mb(peakRss)})`)
  return out
}

// ── sync ────────────────────────────────────────────────────────────────────
await timed('sync-reference', () => syncReference(ctx))
await timed('sync-products-full (initial load, 2 scopes)', () => syncProductsFull(ctx))
await timed('sync-inventory (full sweep)', () => syncInventory(ctx))
await timed('sync-products-incremental (no changes)', () => syncProductsIncremental(ctx))
await new Promise(resolve => {
  child.once('message', resolve)
  child.send({ touch: 500 })
})
const inc = await timed('sync-products-incremental (500 edited)', () => syncProductsIncremental(ctx))
report('  products picked up', String(inc['products:all']))
await timed('sync-products-full (nightly, no changes)', () => syncProductsFull(ctx))
clearInterval(sampler)

const [{ size }] = await db.execute<{ size: string }>('select pg_size_pretty(pg_database_size(current_database())) as size' as never)
report('Database size', size)

// ── Catalog API latency ─────────────────────────────────────────────────────
const logger = createLogger('perf-api', 'warn')
const app = buildApp({ db, token: TOKEN, defaultStore: 'default', logger })
const base = await app.listen({ host: '127.0.0.1', port: 0 })
const skus = (await db.execute<{ sku: string }>('select sku from catalog.products order by random() limit 500' as never)).map(r => r.sku)
const pick = <T,>(xs: T[]) => xs[Math.floor(Math.random() * xs.length)]!
const words = ['linen', 'oak lamp', 'enamel', 'vintage chair', 'cfg12', 'sku40', 'wool throw', 'relaxed dress', 'zzz-no-match']

async function bench(label: string, n: number, makePath: () => string, concurrency = 10) {
  const times: number[] = []
  let errors = 0
  let next = 0
  await Promise.all(
    Array.from({ length: concurrency }, async () => {
      while (next < n) {
        next++
        const t = performance.now()
        const res = await fetch(`${base}${makePath()}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
        await res.arrayBuffer()
        if (!res.ok) errors++
        times.push(performance.now() - t)
      }
    }),
  )
  times.sort((a, b) => a - b)
  const q = (p: number) => times[Math.min(times.length - 1, Math.floor(p * times.length))]!.toFixed(0)
  report(label, `p50 ${q(0.5)} ms · p95 ${q(0.95)} ms · max ${q(1)} ms${errors ? ` · ${errors} errors` : ''}`)
}

await bench('GET /products?q= (search)', 300, () => `/products?q=${encodeURIComponent(pick(words))}&limit=20`)
await bench('GET /products?category=&in_stock=true', 200, () => `/products?category=${3 + Math.floor(Math.random() * 200)}&in_stock=true`)
await bench('GET /products/:sku (full detail)', 300, () => `/products/${encodeURIComponent(pick(skus))}`)
await bench('GET /products/:sku/visibility', 200, () => `/products/${encodeURIComponent(pick(skus))}/visibility`)
await bench('GET /inventory/:sku', 300, () => `/inventory/${encodeURIComponent(pick(skus))}`)
await bench('GET /categories (tree + counts)', 50, () => '/categories')
await bench('GET /quality/issues?type=missing_images', 50, () => '/quality/issues?type=missing_images&limit=25')
await bench('GET /quality/issues?type=missing_required_attributes', 50, () => '/quality/issues?type=missing_required_attributes&limit=25')
await bench('GET /quality/summary (all 9 checks)', 10, () => '/quality/summary', 2)

await app.close()
await close()
child.kill()
